Merge "Add the systemserver_fragment to media SDK." am: 12e479b70e am: c6af5600fd am: d869638896
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1853173
Change-Id: Ib3fd61dbbfbdba03b23ad55d6804ea988c6cab68
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 9eb7bb71..84d05c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2918,6 +2918,13 @@
reasonCode, reason).sendToTarget();
}
reportTempWhitelistChangedLocked(uid, true);
+ } else {
+ // The uid is already temp allowlisted, only need to update AMS for temp allowlist
+ // duration.
+ if (mLocalActivityManager != null) {
+ mLocalActivityManager.updateDeviceIdleTempAllowlist(null, uid, true,
+ duration, tempAllowListType, reasonCode, reason, callingUid);
+ }
}
}
if (informWhitelistChanged) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 591e8ba..4becc6b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1008,13 +1008,21 @@
}
@Override
- public void onUserUnlocked(@NonNull TargetUser user) {
+ public void onUserStarting(@NonNull TargetUser user) {
synchronized (mLock) {
- // Note that the user has started after its unlocked instead of when the user
- // actually starts because the storage won't be decrypted until unlock.
mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier());
}
- // Let's kick any outstanding jobs for this user.
+ // The user is starting but credential encrypted storage is still locked.
+ // Only direct-boot-aware jobs can safely run.
+ // Let's kick off any eligible jobs for this user.
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
+
+ @Override
+ public void onUserUnlocked(@NonNull TargetUser user) {
+ // The user is fully unlocked and credential encrypted storage is now decrypted.
+ // Direct-boot-UNaware jobs can now safely run.
+ // Let's kick off any outstanding jobs for this user.
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp
index b2b66c2..3534624 100644
--- a/cmds/bootanimation/Android.bp
+++ b/cmds/bootanimation/Android.bp
@@ -71,7 +71,7 @@
"libui",
"libjnigraphics",
"libEGL",
- "libGLESv1_CM",
+ "libGLESv2",
"libgui",
],
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 3109c5c..6c8cab7 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -52,9 +52,8 @@
#include <gui/DisplayEventReceiver.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
#include <EGL/eglext.h>
#include "BootAnimation.h"
@@ -108,6 +107,93 @@
static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
static constexpr size_t TEXT_POS_LEN_MAX = 16;
+static const int DYNAMIC_COLOR_COUNT = 4;
+static const char U_TEXTURE[] = "uTexture";
+static const char U_FADE[] = "uFade";
+static const char U_CROP_AREA[] = "uCropArea";
+static const char U_START_COLOR_PREFIX[] = "uStartColor";
+static const char U_END_COLOR_PREFIX[] = "uEndColor";
+static const char U_COLOR_PROGRESS[] = "uColorProgress";
+static const char A_UV[] = "aUv";
+static const char A_POSITION[] = "aPosition";
+static const char VERTEX_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ attribute vec4 aPosition;
+ attribute highp vec2 aUv;
+ varying highp vec2 vUv;
+ void main() {
+ gl_Position = aPosition;
+ vUv = aUv;
+ })";
+static const char IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ const float cWhiteMaskThreshold = 0.05;
+ uniform sampler2D uTexture;
+ uniform float uFade;
+ uniform float uColorProgress;
+ uniform vec4 uStartColor0;
+ uniform vec4 uStartColor1;
+ uniform vec4 uStartColor2;
+ uniform vec4 uStartColor3;
+ uniform vec4 uEndColor0;
+ uniform vec4 uEndColor1;
+ uniform vec4 uEndColor2;
+ uniform vec4 uEndColor3;
+ varying highp vec2 vUv;
+ void main() {
+ vec4 mask = texture2D(uTexture, vUv);
+ float r = mask.r;
+ float g = mask.g;
+ float b = mask.b;
+ float a = mask.a;
+ // If all channels have values, render pixel as a shade of white.
+ float useWhiteMask = step(cWhiteMaskThreshold, r)
+ * step(cWhiteMaskThreshold, g)
+ * step(cWhiteMaskThreshold, b)
+ * step(cWhiteMaskThreshold, a);
+ vec4 color = r * mix(uStartColor0, uEndColor0, uColorProgress)
+ + g * mix(uStartColor1, uEndColor1, uColorProgress)
+ + b * mix(uStartColor2, uEndColor2, uColorProgress)
+ + a * mix(uStartColor3, uEndColor3, uColorProgress);
+ color = mix(color, vec4(vec3((r + g + b + a) * 0.25), 1.0), useWhiteMask);
+ gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+ })";
+static const char IMAGE_FRAG_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ uniform sampler2D uTexture;
+ uniform float uFade;
+ varying highp vec2 vUv;
+ void main() {
+ vec4 color = texture2D(uTexture, vUv);
+ gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+ })";
+static const char TEXT_FRAG_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ uniform sampler2D uTexture;
+ uniform vec4 uCropArea;
+ varying highp vec2 vUv;
+ void main() {
+ vec2 uv = vec2(mix(uCropArea.x, uCropArea.z, vUv.x),
+ mix(uCropArea.y, uCropArea.w, vUv.y));
+ gl_FragColor = texture2D(uTexture, uv);
+ })";
+
+static GLfloat quadPositions[] = {
+ -0.5f, -0.5f,
+ +0.5f, -0.5f,
+ +0.5f, +0.5f,
+ +0.5f, +0.5f,
+ -0.5f, +0.5f,
+ -0.5f, -0.5f
+};
+static GLfloat quadUVs[] = {
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ 1.0f, 0.0f,
+ 0.0f, 0.0f,
+ 0.0f, 1.0f
+};
// ---------------------------------------------------------------------------
@@ -163,7 +249,8 @@
requestExit();
}
-static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo) {
+static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo,
+ bool premultiplyAlpha) {
AImageDecoder* decoder = nullptr;
AImageDecoder_createFromBuffer(encodedData, dataLength, &decoder);
if (!decoder) {
@@ -177,6 +264,10 @@
outInfo->stride = AImageDecoder_getMinimumStride(decoder);
outInfo->flags = 0;
+ if (!premultiplyAlpha) {
+ AImageDecoder_setUnpremultipliedRequired(decoder, true);
+ }
+
const size_t size = outInfo->stride * outInfo->height;
void* pixels = malloc(size);
int result = AImageDecoder_decodeImage(decoder, pixels, outInfo->stride, size);
@@ -190,13 +281,14 @@
}
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
- const char* name) {
+ const char* name, bool premultiplyAlpha) {
Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
if (asset == nullptr)
return NO_INIT;
AndroidBitmapInfo bitmapInfo;
- void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo);
+ void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo,
+ premultiplyAlpha);
auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };
asset->close();
@@ -209,7 +301,6 @@
const int w = bitmapInfo.width;
const int h = bitmapInfo.height;
- GLint crop[4] = { 0, h, w, -h };
texture->w = w;
texture->h = h;
@@ -237,18 +328,19 @@
break;
}
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return NO_ERROR;
}
-status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) {
+status_t BootAnimation::initTexture(FileMap* map, int* width, int* height,
+ bool premultiplyAlpha) {
AndroidBitmapInfo bitmapInfo;
- void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo);
+ void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo,
+ premultiplyAlpha);
auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };
// FileMap memory is never released until application exit.
@@ -263,7 +355,6 @@
const int w = bitmapInfo.width;
const int h = bitmapInfo.height;
- GLint crop[4] = { 0, h, w, -h };
int tw = 1 << (31 - __builtin_clz(w));
int th = 1 << (31 - __builtin_clz(h));
if (tw < w) tw <<= 1;
@@ -297,7 +388,10 @@
break;
}
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
*width = w;
*height = h;
@@ -470,7 +564,9 @@
eglInitialize(display, nullptr, nullptr);
EGLConfig config = getEglConfig(display);
EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
- EGLContext context = eglCreateContext(display, config, nullptr, nullptr);
+ // Initialize egl context with client version number 2.0.
+ EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+ EGLContext context = eglCreateContext(display, config, nullptr, contextAttributes);
EGLint w, h;
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
@@ -503,11 +599,6 @@
void BootAnimation::projectSceneToWindow() {
glViewport(0, 0, mWidth, mHeight);
glScissor(0, 0, mWidth, mHeight);
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrthof(0, static_cast<float>(mWidth), 0, static_cast<float>(mHeight), -1, 1);
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
}
void BootAnimation::resizeSurface(int newWidth, int newHeight) {
@@ -600,8 +691,76 @@
}
}
+GLuint compileShader(GLenum shaderType, const GLchar *source) {
+ GLuint shader = glCreateShader(shaderType);
+ glShaderSource(shader, 1, &source, 0);
+ glCompileShader(shader);
+ GLint isCompiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
+ if (isCompiled == GL_FALSE) {
+ SLOGE("Compile shader failed. Shader type: %d", shaderType);
+ GLint maxLength = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
+ std::vector<GLchar> errorLog(maxLength);
+ glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]);
+ SLOGE("Shader compilation error: %s", &errorLog[0]);
+ return 0;
+ }
+ return shader;
+}
+
+GLuint linkShader(GLuint vertexShader, GLuint fragmentShader) {
+ GLuint program = glCreateProgram();
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, fragmentShader);
+ glLinkProgram(program);
+ GLint isLinked = 0;
+ glGetProgramiv(program, GL_LINK_STATUS, (int *)&isLinked);
+ if (isLinked == GL_FALSE) {
+ SLOGE("Linking shader failed. Shader handles: vert %d, frag %d",
+ vertexShader, fragmentShader);
+ return 0;
+ }
+ return program;
+}
+
+void BootAnimation::initShaders() {
+ bool dynamicColoringEnabled = mAnimation != nullptr && mAnimation->dynamicColoringEnabled;
+ GLuint vertexShader = compileShader(GL_VERTEX_SHADER, (const GLchar *)VERTEX_SHADER_SOURCE);
+ GLuint imageFragmentShader =
+ compileShader(GL_FRAGMENT_SHADER, dynamicColoringEnabled
+ ? (const GLchar *)IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE
+ : (const GLchar *)IMAGE_FRAG_SHADER_SOURCE);
+ GLuint textFragmentShader =
+ compileShader(GL_FRAGMENT_SHADER, (const GLchar *)TEXT_FRAG_SHADER_SOURCE);
+
+ // Initialize image shader.
+ mImageShader = linkShader(vertexShader, imageFragmentShader);
+ GLint positionLocation = glGetAttribLocation(mImageShader, A_POSITION);
+ GLint uvLocation = glGetAttribLocation(mImageShader, A_UV);
+ mImageTextureLocation = glGetUniformLocation(mImageShader, U_TEXTURE);
+ mImageFadeLocation = glGetUniformLocation(mImageShader, U_FADE);
+ glEnableVertexAttribArray(positionLocation);
+ glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, quadPositions);
+ glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
+ glEnableVertexAttribArray(uvLocation);
+
+ // Initialize text shader.
+ mTextShader = linkShader(vertexShader, textFragmentShader);
+ positionLocation = glGetAttribLocation(mTextShader, A_POSITION);
+ uvLocation = glGetAttribLocation(mTextShader, A_UV);
+ mTextTextureLocation = glGetUniformLocation(mTextShader, U_TEXTURE);
+ mTextCropAreaLocation = glGetUniformLocation(mTextShader, U_CROP_AREA);
+ glEnableVertexAttribArray(positionLocation);
+ glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, quadPositions);
+ glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
+ glEnableVertexAttribArray(uvLocation);
+}
+
bool BootAnimation::threadLoop() {
bool result;
+ initShaders();
+
// We have no bootanimation file, so we use the stock android logo
// animation.
if (mZipFileName.isEmpty()) {
@@ -623,6 +782,8 @@
}
bool BootAnimation::android() {
+ glActiveTexture(GL_TEXTURE0);
+
SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
@@ -631,19 +792,16 @@
mCallbacks->init({});
// clear screen
- glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
+ glUseProgram(mImageShader);
+
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
- glEnable(GL_TEXTURE_2D);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-
// Blend state
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const nsecs_t startTime = systemTime();
do {
@@ -666,12 +824,12 @@
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
- glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
- glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
+ drawTexturedQuad(x, yc, mAndroid[1].w, mAndroid[1].h);
+ drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
- glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
+ drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
@@ -766,6 +924,20 @@
return true;
}
+// Parse a color represented as a signed decimal int string.
+// E.g. "-2757722" (whose hex 2's complement is 0xFFD5EBA6).
+// If the input color string is empty, set color with values in defaultColor.
+static void parseColorDecimalString(const std::string& colorString,
+ float color[3], float defaultColor[3]) {
+ if (colorString == "") {
+ memcpy(color, defaultColor, sizeof(float) * 3);
+ return;
+ }
+ int colorInt = atoi(colorString.c_str());
+ color[0] = ((float)((colorInt >> 16) & 0xFF)) / 0xFF; // r
+ color[1] = ((float)((colorInt >> 8) & 0xFF)) / 0xFF; // g
+ color[2] = ((float)(colorInt & 0xFF)) / 0xFF; // b
+}
static bool readFile(ZipFileRO* zip, const char* name, String8& outString) {
ZipEntryRO entry = zip->findEntryByName(name);
@@ -798,10 +970,10 @@
status = initTexture(font->map, &font->texture.w, &font->texture.h);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else if (fallback != nullptr) {
status = initTexture(&font->texture, mAssets, fallback);
} else {
@@ -816,40 +988,11 @@
return status;
}
-void BootAnimation::fadeFrame(const int frameLeft, const int frameBottom, const int frameWidth,
- const int frameHeight, const Animation::Part& part,
- const int fadedFramesCount) {
- glEnable(GL_BLEND);
- glEnableClientState(GL_VERTEX_ARRAY);
- glDisable(GL_TEXTURE_2D);
- // avoid creating a hole due to mixing result alpha with GL_REPLACE texture
- glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
-
- const float alpha = static_cast<float>(fadedFramesCount) / part.framesToFadeCount;
- glColor4f(part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], alpha);
-
- const float frameStartX = static_cast<float>(frameLeft);
- const float frameStartY = static_cast<float>(frameBottom);
- const float frameEndX = frameStartX + frameWidth;
- const float frameEndY = frameStartY + frameHeight;
- const GLfloat frameRect[] = {
- frameStartX, frameStartY,
- frameEndX, frameStartY,
- frameEndX, frameEndY,
- frameStartX, frameEndY
- };
- glVertexPointer(2, GL_FLOAT, 0, frameRect);
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glEnable(GL_TEXTURE_2D);
- glDisableClientState(GL_VERTEX_ARRAY);
- glDisable(GL_BLEND);
-}
-
void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
glEnable(GL_BLEND); // Allow us to draw on top of the animation
glBindTexture(GL_TEXTURE_2D, font.texture.name);
+ glUseProgram(mTextShader);
+ glUniform1i(mTextTextureLocation, 0);
const int len = strlen(str);
const int strWidth = font.char_width * len;
@@ -865,8 +1008,6 @@
*y = mHeight + *y - font.char_height;
}
- int cropRect[4] = { 0, 0, font.char_width, -font.char_height };
-
for (int i = 0; i < len; i++) {
char c = str[i];
@@ -878,13 +1019,13 @@
const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters
const int row = charPos / FONT_NUM_COLS;
const int col = charPos % FONT_NUM_COLS;
- cropRect[0] = col * font.char_width; // Left of column
- cropRect[1] = row * font.char_height * 2; // Top of row
- // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line
- cropRect[1] += bold ? 2 * font.char_height : font.char_height;
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
-
- glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height);
+ // Bold fonts are expected in the second half of each row.
+ float v0 = (row + (bold ? 0.5f : 0.0f)) / FONT_NUM_ROWS;
+ float u0 = ((float)col) / FONT_NUM_COLS;
+ float v1 = v0 + 1.0f / FONT_NUM_ROWS / 2;
+ float u1 = u0 + 1.0f / FONT_NUM_COLS;
+ glUniform4f(mTextCropAreaLocation, u0, v0, u1, v1);
+ drawTexturedQuad(*x, *y, font.char_width, font.char_height);
*x += font.char_width;
}
@@ -938,6 +1079,8 @@
return false;
}
char const* s = desString.string();
+ std::string dynamicColoringPartName = "";
+ bool postDynamicColoring = false;
// Parse the description file
for (;;) {
@@ -952,11 +1095,19 @@
int pause = 0;
int progress = 0;
int framesToFadeCount = 0;
+ int colorTransitionStart = 0;
+ int colorTransitionEnd = 0;
char path[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified
char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
+ char dynamicColoringPartNameBuffer[ANIM_ENTRY_NAME_MAX];
char pathType;
+ // start colors default to black if unspecified
+ char start_color_0[7] = "000000";
+ char start_color_1[7] = "000000";
+ char start_color_2[7] = "000000";
+ char start_color_3[7] = "000000";
int nextReadPos;
@@ -971,6 +1122,18 @@
} else {
animation.progressEnabled = false;
}
+ } else if (sscanf(l, "dynamic_colors %" STRTO(ANIM_PATH_MAX) "s #%6s #%6s #%6s #%6s %d %d",
+ dynamicColoringPartNameBuffer,
+ start_color_0, start_color_1, start_color_2, start_color_3,
+ &colorTransitionStart, &colorTransitionEnd)) {
+ animation.dynamicColoringEnabled = true;
+ parseColor(start_color_0, animation.startColors[0]);
+ parseColor(start_color_1, animation.startColors[1]);
+ parseColor(start_color_2, animation.startColors[2]);
+ parseColor(start_color_3, animation.startColors[3]);
+ animation.colorTransitionStart = colorTransitionStart;
+ animation.colorTransitionEnd = colorTransitionEnd;
+ dynamicColoringPartName = std::string(dynamicColoringPartNameBuffer);
} else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n",
&pathType, &count, &pause, path, &nextReadPos) >= 4) {
if (pathType == 'f') {
@@ -983,6 +1146,16 @@
// "clockPos1=%s, clockPos2=%s",
// pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2);
Animation::Part part;
+ if (path == dynamicColoringPartName) {
+ // Part is specified to use dynamic coloring.
+ part.useDynamicColoring = true;
+ part.postDynamicColoring = false;
+ postDynamicColoring = true;
+ } else {
+ // Part does not use dynamic coloring.
+ part.useDynamicColoring = false;
+ part.postDynamicColoring = postDynamicColoring;
+ }
part.playUntilComplete = pathType == 'c';
part.framesToFadeCount = framesToFadeCount;
part.count = count;
@@ -1166,19 +1339,16 @@
// Blend required to draw time on top of animation frames.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
- glBindTexture(GL_TEXTURE_2D, 0);
glEnable(GL_TEXTURE_2D);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
bool clockFontInitialized = false;
if (mClockEnabled) {
clockFontInitialized =
@@ -1193,6 +1363,10 @@
mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
}
+ if (mAnimation != nullptr && mAnimation->dynamicColoringEnabled) {
+ initDynamicColors();
+ }
+
playAnimation(*mAnimation);
if (mTimeCheckThread != nullptr) {
@@ -1218,6 +1392,55 @@
(lastDisplayedProgress == 0 || lastDisplayedProgress == 100);
}
+// Linear mapping from range <a1, a2> to range <b1, b2>
+float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+}
+
+void BootAnimation::drawTexturedQuad(float xStart, float yStart, float width, float height) {
+ // Map coordinates from screen space to world space.
+ float x0 = mapLinear(xStart, 0, mWidth, -1, 1);
+ float y0 = mapLinear(yStart, 0, mHeight, -1, 1);
+ float x1 = mapLinear(xStart + width, 0, mWidth, -1, 1);
+ float y1 = mapLinear(yStart + height, 0, mHeight, -1, 1);
+ // Update quad vertex positions.
+ quadPositions[0] = x0;
+ quadPositions[1] = y0;
+ quadPositions[2] = x1;
+ quadPositions[3] = y0;
+ quadPositions[4] = x1;
+ quadPositions[5] = y1;
+ quadPositions[6] = x1;
+ quadPositions[7] = y1;
+ quadPositions[8] = x0;
+ quadPositions[9] = y1;
+ quadPositions[10] = x0;
+ quadPositions[11] = y0;
+ glDrawArrays(GL_TRIANGLES, 0,
+ sizeof(quadPositions) / sizeof(quadPositions[0]) / 2);
+}
+
+void BootAnimation::initDynamicColors() {
+ for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+ parseColorDecimalString(
+ android::base::GetProperty("persist.bootanim.color" + std::to_string(i + 1), ""),
+ mAnimation->endColors[i], mAnimation->startColors[i]);
+ }
+ glUseProgram(mImageShader);
+ SLOGI("[BootAnimation] Dynamically coloring boot animation.");
+ for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+ float *startColor = mAnimation->startColors[i];
+ float *endColor = mAnimation->endColors[i];
+ glUniform4f(glGetUniformLocation(mImageShader,
+ (U_START_COLOR_PREFIX + std::to_string(i)).c_str()),
+ startColor[0], startColor[1], startColor[2], 1 /* alpha */);
+ glUniform4f(glGetUniformLocation(mImageShader,
+ (U_END_COLOR_PREFIX + std::to_string(i)).c_str()),
+ endColor[0], endColor[1], endColor[2], 1 /* alpha */);
+ }
+ mImageColorProgressLocation = glGetUniformLocation(mImageShader, U_COLOR_PROGRESS);
+}
+
bool BootAnimation::playAnimation(const Animation& animation) {
const size_t pcount = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
@@ -1230,7 +1453,6 @@
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
- glBindTexture(GL_TEXTURE_2D, 0);
// Handle animation package
if (part.animation != nullptr) {
@@ -1261,6 +1483,19 @@
for (size_t j=0 ; j<fcount ; j++) {
if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;
+ // Color progress is
+ // - the animation progress, normalized from
+ // [colorTransitionStart,colorTransitionEnd] to [0, 1] for the dynamic coloring
+ // part.
+ // - 0 for parts that come before,
+ // - 1 for parts that come after.
+ float colorProgress = part.useDynamicColoring
+ ? fmin(fmax(
+ ((float)j - animation.colorTransitionStart) /
+ fmax(animation.colorTransitionEnd -
+ animation.colorTransitionStart, 1.0f), 0.0f), 1.0f)
+ : (part.postDynamicColoring ? 1 : 0);
+
processDisplayEvents();
const int animationX = (mWidth - animation.width) / 2;
@@ -1272,44 +1507,38 @@
if (r > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
- if (part.count != 1) {
- glGenTextures(1, &frame.tid);
- glBindTexture(GL_TEXTURE_2D, frame.tid);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- }
+ glGenTextures(1, &frame.tid);
+ glBindTexture(GL_TEXTURE_2D, frame.tid);
int w, h;
- initTexture(frame.map, &w, &h);
+ // Set decoding option to alpha unpremultiplied so that the R, G, B channels
+ // of transparent pixels are preserved.
+ initTexture(frame.map, &w, &h, false /* don't premultiply alpha */);
}
const int xc = animationX + frame.trimX;
const int yc = animationY + frame.trimY;
- Region clearReg(Rect(mWidth, mHeight));
- clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
- if (!clearReg.isEmpty()) {
- Region::const_iterator head(clearReg.begin());
- Region::const_iterator tail(clearReg.end());
- glEnable(GL_SCISSOR_TEST);
- while (head != tail) {
- const Rect& r2(*head++);
- glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
- glClear(GL_COLOR_BUFFER_BIT);
- }
- glDisable(GL_SCISSOR_TEST);
- }
+ glClear(GL_COLOR_BUFFER_BIT);
// specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
// which is equivalent to mHeight - (yc + frame.trimHeight)
const int frameDrawY = mHeight - (yc + frame.trimHeight);
- glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight);
+ float fade = 0;
// if the part hasn't been stopped yet then continue fading if necessary
if (exitPending() && part.hasFadingPhase()) {
- fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part,
- ++fadedFramesCount);
+ fade = static_cast<float>(++fadedFramesCount) / part.framesToFadeCount;
if (fadedFramesCount >= part.framesToFadeCount) {
fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading
}
}
+ glUseProgram(mImageShader);
+ glUniform1i(mImageTextureLocation, 0);
+ glUniform1f(mImageFadeLocation, fade);
+ if (animation.dynamicColoringEnabled) {
+ glUniform1f(mImageColorProgressLocation, colorProgress);
+ }
+ glEnable(GL_BLEND);
+ drawTexturedQuad(xc, frameDrawY, frame.trimWidth, frame.trimHeight);
+ glDisable(GL_BLEND);
if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index f8a31c6..7a597da 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -31,7 +31,7 @@
#include <binder/IBinder.h>
#include <EGL/egl.h>
-#include <GLES/gl.h>
+#include <GLES2/gl2.h>
namespace android {
@@ -53,7 +53,7 @@
};
struct Font {
- FileMap* map;
+ FileMap* map = nullptr;
Texture texture;
int char_width;
int char_height;
@@ -62,7 +62,7 @@
struct Animation {
struct Frame {
String8 name;
- FileMap* map;
+ FileMap* map = nullptr;
int trimX;
int trimY;
int trimWidth;
@@ -90,6 +90,10 @@
uint8_t* audioData;
int audioLength;
Animation* animation;
+ // Controls if dynamic coloring is enabled for this part.
+ bool useDynamicColoring = false;
+ // Defines if this part is played after the dynamic coloring part.
+ bool postDynamicColoring = false;
bool hasFadingPhase() const {
return !playUntilComplete && framesToFadeCount > 0;
@@ -105,6 +109,12 @@
ZipFileRO* zip;
Font clockFont;
Font progressFont;
+ // Controls if dynamic coloring is enabled for the whole animation.
+ bool dynamicColoringEnabled = false;
+ int colorTransitionStart = 0; // Start frame of dynamic color transition.
+ int colorTransitionEnd = 0; // End frame of dynamic color transition.
+ float startColors[4][3]; // Start colors of dynamic color transition.
+ float endColors[4][3]; // End colors of dynamic color transition.
};
// All callbacks will be called from this class's internal thread.
@@ -163,9 +173,12 @@
int displayEventCallback(int fd, int events, void* data);
void processDisplayEvents();
- status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
- status_t initTexture(FileMap* map, int* width, int* height);
+ status_t initTexture(Texture* texture, AssetManager& asset, const char* name,
+ bool premultiplyAlpha = true);
+ status_t initTexture(FileMap* map, int* width, int* height,
+ bool premultiplyAlpha = true);
status_t initFont(Font* font, const char* fallback);
+ void initShaders();
bool android();
bool movie();
void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
@@ -173,6 +186,7 @@
void drawProgress(int percent, const Font& font, const int xPos, const int yPos);
void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight,
const Animation::Part& part, int fadedFramesCount);
+ void drawTexturedQuad(float xStart, float yStart, float width, float height);
bool validClock(const Animation::Part& part);
Animation* loadAnimation(const String8&);
bool playAnimation(const Animation&);
@@ -192,6 +206,7 @@
void checkExit();
void handleViewport(nsecs_t timestep);
+ void initDynamicColors();
sp<SurfaceComposerClient> mSession;
AssetManager mAssets;
@@ -218,6 +233,13 @@
sp<TimeCheckThread> mTimeCheckThread = nullptr;
sp<Callbacks> mCallbacks;
Animation* mAnimation = nullptr;
+ GLuint mImageShader;
+ GLuint mTextShader;
+ GLuint mImageFadeLocation;
+ GLuint mImageTextureLocation;
+ GLuint mTextCropAreaLocation;
+ GLuint mTextTextureLocation;
+ GLuint mImageColorProgressLocation;
};
// ---------------------------------------------------------------------------
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4376d22..f53c5b6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -771,6 +771,11 @@
return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}
+ /** @hide Should this process state be considered in the cache? */
+ public static final boolean isProcStateCached(int procState) {
+ return procState >= PROCESS_STATE_CACHED_ACTIVITY;
+ }
+
/** @hide Is this a foreground service type? */
public static boolean isForegroundService(int procState) {
return procState == PROCESS_STATE_FOREGROUND_SERVICE;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0d68df4..4e8480c 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -156,6 +156,7 @@
/**
* Update information about which app IDs are on the temp allowlist.
* @param appids the updated list of appIds in temp allowlist.
+ * If null, it is to update only changingUid.
* @param changingUid uid to add or remove to temp allowlist.
* @param adding true to add to temp allowlist, false to remove from temp allowlist.
* @param durationMs when adding is true, the duration to be in temp allowlist.
@@ -165,7 +166,7 @@
* @param callingUid the callingUid that setup this temp allowlist, only valid when param adding
* is true.
*/
- public abstract void updateDeviceIdleTempAllowlist(int[] appids, int changingUid,
+ public abstract void updateDeviceIdleTempAllowlist(@Nullable int[] appids, int changingUid,
boolean adding, long durationMs, @TempAllowListType int type,
@ReasonCode int reasonCode,
@Nullable String reason, int callingUid);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c36b585..9272e45 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2735,10 +2735,13 @@
// need to override their display in ResourcesManager.
baseContext.mForceDisplayOverrideInResources = false;
baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
- baseContext.mDisplay = display;
final Resources windowContextResources = createWindowContextResources(baseContext);
baseContext.setResources(windowContextResources);
+ // Associate the display with window context resources so that configuration update from
+ // the server side will also apply to the display's metrics.
+ baseContext.mDisplay = ResourcesManager.getInstance()
+ .getAdjustedDisplay(display.getDisplayId(), windowContextResources);
return baseContext;
}
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 5964f71..babeb08 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -135,6 +135,7 @@
throw e.rethrowFromSystemServer();
}
}
+
/**
* Returns a list of supported game modes for a given package.
* <p>
@@ -151,4 +152,20 @@
}
}
+ /**
+ * Returns if ANGLE is enabled for a given package.
+ * <p>
+ * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+ *
+ * @hide
+ */
+ @UserHandleAware
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+ public @GameMode boolean isAngleEnabled(@NonNull String packageName) {
+ try {
+ return mService.getAngleEnabled(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 4bf8a3f..189f0a2 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -23,4 +23,5 @@
int getGameMode(String packageName, int userId);
void setGameMode(String packageName, int gameMode, int userId);
int[] getAvailableGameModes(String packageName);
+ boolean getAngleEnabled(String packageName, int userId);
}
\ No newline at end of file
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4b054f4..61b1abe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1884,6 +1884,14 @@
* clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
* activity itself.
*
+ * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
+ * both are displayed or required, depends on where and how the action is used, and the
+ * {@link Style} applied to the Notification.
+ *
+ * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
+ * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
+ * with an altered in luminance to ensure proper contrast within the Notification.
+ *
* @param icon icon to show for this action
* @param title the title of the action
* @param intent the {@link PendingIntent} to fire when users trigger this action
@@ -5387,8 +5395,8 @@
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// Use different highlighted colors for conversations' unread count
if (p.mHighlightExpander) {
- pillColor = Colors.flattenAlpha(getPrimaryAccentColor(p), bgColor);
- textColor = Colors.flattenAlpha(bgColor, pillColor);
+ pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
+ textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -6121,21 +6129,22 @@
if (emphasizedMode) {
// change the background bgColor
CharSequence title = action.title;
- ColorStateList[] outResultColor = new ColorStateList[1];
int buttonFillColor = getColors(p).getSecondaryAccentColor();
if (isLegacy()) {
title = ContrastColorUtil.clearColorSpans(title);
} else {
- int notifBackgroundColor = getColors(p).getBackgroundColor();
- title = ensureColorSpanContrast(title, notifBackgroundColor, outResultColor);
+ // Check for a full-length span color to use as the button fill color.
+ Integer fullLengthColor = getFullLengthSpanColor(title);
+ if (fullLengthColor != null) {
+ // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
+ int notifBackgroundColor = getColors(p).getBackgroundColor();
+ buttonFillColor = ensureButtonFillContrast(
+ fullLengthColor, notifBackgroundColor);
+ }
+ // Remove full-length color spans and ensure text contrast with the button fill.
+ title = ensureColorSpanContrast(title, buttonFillColor);
}
button.setTextViewText(R.id.action0, processTextSpans(title));
- boolean hasColorOverride = outResultColor[0] != null;
- if (hasColorOverride) {
- // There's a span spanning the full text, let's take it and use it as the
- // background color
- buttonFillColor = outResultColor[0].getDefaultColor();
- }
final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
buttonFillColor, mInNightMode);
button.setTextColor(R.id.action0, textColor);
@@ -6168,17 +6177,58 @@
}
/**
- * Ensures contrast on color spans against a background color. also returns the color of the
- * text if a span was found that spans over the whole text.
+ * Extract the color from a full-length span from the text.
+ *
+ * @param charSequence the charSequence containing spans
+ * @return the raw color of the text's last full-length span containing a color, or null if
+ * no full-length span sets the text color.
+ * @hide
+ */
+ @VisibleForTesting
+ @Nullable
+ public static Integer getFullLengthSpanColor(CharSequence charSequence) {
+ // NOTE: this method preserves the functionality that for a CharSequence with multiple
+ // full-length spans, the color of the last one is used.
+ Integer result = null;
+ if (charSequence instanceof Spanned) {
+ Spanned ss = (Spanned) charSequence;
+ Object[] spans = ss.getSpans(0, ss.length(), Object.class);
+ // First read through all full-length spans to get the button fill color, which will
+ // be used as the background color for ensuring contrast of non-full-length spans.
+ for (Object span : spans) {
+ int spanStart = ss.getSpanStart(span);
+ int spanEnd = ss.getSpanEnd(span);
+ boolean fullLength = (spanEnd - spanStart) == charSequence.length();
+ if (!fullLength) {
+ continue;
+ }
+ if (span instanceof TextAppearanceSpan) {
+ TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
+ ColorStateList textColor = originalSpan.getTextColor();
+ if (textColor != null) {
+ result = textColor.getDefaultColor();
+ }
+ } else if (span instanceof ForegroundColorSpan) {
+ ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
+ result = originalSpan.getForegroundColor();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Ensures contrast on color spans against a background color.
+ * Note that any full-length color spans will be removed instead of being contrasted.
*
* @param charSequence the charSequence on which the spans are
* @param background the background color to ensure the contrast against
- * @param outResultColor an array in which a color will be returned as the first element if
- * there exists a full length color span.
* @return the contrasted charSequence
+ * @hide
*/
- private static CharSequence ensureColorSpanContrast(CharSequence charSequence,
- int background, ColorStateList[] outResultColor) {
+ @VisibleForTesting
+ public static CharSequence ensureColorSpanContrast(CharSequence charSequence,
+ int background) {
if (charSequence instanceof Spanned) {
Spanned ss = (Spanned) charSequence;
Object[] spans = ss.getSpans(0, ss.length(), Object.class);
@@ -6195,19 +6245,19 @@
TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
ColorStateList textColor = originalSpan.getTextColor();
if (textColor != null) {
- int[] colors = textColor.getColors();
- int[] newColors = new int[colors.length];
- for (int i = 0; i < newColors.length; i++) {
- boolean isBgDark = isColorDark(background);
- newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
- colors[i], background, isBgDark);
- }
- textColor = new ColorStateList(textColor.getStates().clone(),
- newColors);
if (fullLength) {
- outResultColor[0] = textColor;
// Let's drop the color from the span
textColor = null;
+ } else {
+ int[] colors = textColor.getColors();
+ int[] newColors = new int[colors.length];
+ for (int i = 0; i < newColors.length; i++) {
+ boolean isBgDark = isColorDark(background);
+ newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
+ colors[i], background, isBgDark);
+ }
+ textColor = new ColorStateList(textColor.getStates().clone(),
+ newColors);
}
resultSpan = new TextAppearanceSpan(
originalSpan.getFamily(),
@@ -6217,15 +6267,14 @@
originalSpan.getLinkTextColor());
}
} else if (resultSpan instanceof ForegroundColorSpan) {
- ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
- int foregroundColor = originalSpan.getForegroundColor();
- boolean isBgDark = isColorDark(background);
- foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
- foregroundColor, background, isBgDark);
if (fullLength) {
- outResultColor[0] = ColorStateList.valueOf(foregroundColor);
resultSpan = null;
} else {
+ ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
+ int foregroundColor = originalSpan.getForegroundColor();
+ boolean isBgDark = isColorDark(background);
+ foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
+ foregroundColor, background, isBgDark);
resultSpan = new ForegroundColorSpan(foregroundColor);
}
} else {
@@ -6247,13 +6296,29 @@
*
* @param color the color to check
* @return true if the color has higher contrast with white than black
+ * @hide
*/
- private static boolean isColorDark(int color) {
+ public static boolean isColorDark(int color) {
// as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
}
/**
+ * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
+ * as the original color, but is lightened or darkened depending on whether the background
+ * is dark or light.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static int ensureButtonFillContrast(int color, int bg) {
+ return isColorDark(bg)
+ ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3)
+ : ContrastColorUtil.findContrastColor(color, bg, true, 1.3);
+ }
+
+
+ /**
* @return Whether we are currently building a notification from a legacy (an app that
* doesn't create material notifications by itself) app.
*/
@@ -6441,25 +6506,34 @@
if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
&& !styleDisplaysCustomViewInline()) {
- if (mN.contentView == null) {
- mN.contentView = createContentView();
+ RemoteViews newContentView = mN.contentView;
+ RemoteViews newBigContentView = mN.bigContentView;
+ RemoteViews newHeadsUpContentView = mN.headsUpContentView;
+ if (newContentView == null) {
+ newContentView = createContentView();
mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
- mN.contentView.getSequenceNumber());
+ newContentView.getSequenceNumber());
}
- if (mN.bigContentView == null) {
- mN.bigContentView = createBigContentView();
- if (mN.bigContentView != null) {
+ if (newBigContentView == null) {
+ newBigContentView = createBigContentView();
+ if (newBigContentView != null) {
mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
- mN.bigContentView.getSequenceNumber());
+ newBigContentView.getSequenceNumber());
}
}
- if (mN.headsUpContentView == null) {
- mN.headsUpContentView = createHeadsUpContentView();
- if (mN.headsUpContentView != null) {
+ if (newHeadsUpContentView == null) {
+ newHeadsUpContentView = createHeadsUpContentView();
+ if (newHeadsUpContentView != null) {
mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
- mN.headsUpContentView.getSequenceNumber());
+ newHeadsUpContentView.getSequenceNumber());
}
}
+ // Don't set any of the content views until after they have all been generated,
+ // to avoid the generated .contentView triggering the logic which skips generating
+ // the .bigContentView.
+ mN.contentView = newContentView;
+ mN.bigContentView = newBigContentView;
+ mN.headsUpContentView = newHeadsUpContentView;
}
if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
@@ -12305,6 +12379,8 @@
private int mSecondaryTextColor = COLOR_INVALID;
private int mPrimaryAccentColor = COLOR_INVALID;
private int mSecondaryAccentColor = COLOR_INVALID;
+ private int mTertiaryAccentColor = COLOR_INVALID;
+ private int mOnAccentTextColor = COLOR_INVALID;
private int mErrorColor = COLOR_INVALID;
private int mContrastColor = COLOR_INVALID;
private int mRippleAlpha = 0x33;
@@ -12362,7 +12438,7 @@
if (isColorized) {
if (rawColor == COLOR_DEFAULT) {
- int[] attrs = {R.attr.colorAccentTertiary};
+ int[] attrs = {R.attr.colorAccentSecondary};
try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
mBackgroundColor = getColor(ta, 0, Color.WHITE);
}
@@ -12379,6 +12455,8 @@
mContrastColor = mPrimaryTextColor;
mPrimaryAccentColor = mPrimaryTextColor;
mSecondaryAccentColor = mSecondaryTextColor;
+ mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
+ mOnAccentTextColor = mBackgroundColor;
mErrorColor = mPrimaryTextColor;
mRippleAlpha = 0x33;
} else {
@@ -12389,6 +12467,8 @@
R.attr.textColorSecondary,
R.attr.colorAccent,
R.attr.colorAccentSecondary,
+ R.attr.colorAccentTertiary,
+ R.attr.textColorOnAccent,
R.attr.colorError,
R.attr.colorControlHighlight
};
@@ -12399,8 +12479,10 @@
mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
- mErrorColor = getColor(ta, 6, COLOR_INVALID);
- mRippleAlpha = Color.alpha(getColor(ta, 7, 0x33ffffff));
+ mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
+ mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
+ mErrorColor = getColor(ta, 8, COLOR_INVALID);
+ mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
}
mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
mBackgroundColor, nightMode);
@@ -12420,6 +12502,14 @@
if (mSecondaryAccentColor == COLOR_INVALID) {
mSecondaryAccentColor = mContrastColor;
}
+ if (mTertiaryAccentColor == COLOR_INVALID) {
+ mTertiaryAccentColor = mContrastColor;
+ }
+ if (mOnAccentTextColor == COLOR_INVALID) {
+ mOnAccentTextColor = ColorUtils.setAlphaComponent(
+ ContrastColorUtil.resolvePrimaryColor(
+ ctx, mTertiaryAccentColor, nightMode), 0xFF);
+ }
if (mErrorColor == COLOR_INVALID) {
mErrorColor = mPrimaryTextColor;
}
@@ -12485,6 +12575,16 @@
return mSecondaryAccentColor;
}
+ /** @return the theme's tertiary accent color for colored UI elements. */
+ public @ColorInt int getTertiaryAccentColor() {
+ return mTertiaryAccentColor;
+ }
+
+ /** @return the theme's text color to be used on the tertiary accent color. */
+ public @ColorInt int getOnAccentTextColor() {
+ return mOnAccentTextColor;
+ }
+
/**
* @return the contrast-adjusted version of the color provided by the app, or the
* primary text color when colorized.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1837fb8..6553b61 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -260,8 +260,6 @@
private boolean mDemoted = false;
private boolean mImportantConvo = false;
private long mDeletedTime = DEFAULT_DELETION_TIME_MS;
- // If the sound for this channel is missing, e.g. after restore.
- private boolean mIsSoundMissing;
/**
* Creates a notification channel.
@@ -717,13 +715,6 @@
}
/**
- * @hide
- */
- public boolean isSoundMissing() {
- return mIsSoundMissing;
- }
-
- /**
* Returns the audio attributes for sound played by notifications posted to this channel.
*/
public AudioAttributes getAudioAttributes() {
@@ -1007,9 +998,8 @@
// according to the docs because canonicalize method has to handle canonical uris as well.
Uri canonicalizedUri = contentResolver.canonicalize(uri);
if (canonicalizedUri == null) {
- // We got a null because the uri in the backup does not exist here.
- mIsSoundMissing = true;
- return null;
+ // We got a null because the uri in the backup does not exist here, so we return default
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
}
return contentResolver.uncanonicalize(canonicalizedUri);
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8d332ab..6cfa39c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -559,6 +559,53 @@
return null;
}
+ public Rect peekWallpaperDimensions(Context context, boolean returnDefault, int userId) {
+ if (mService != null) {
+ try {
+ if (!mService.isWallpaperSupported(context.getOpPackageName())) {
+ return new Rect();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ Rect dimensions = null;
+ synchronized (this) {
+ try {
+ Bundle params = new Bundle();
+ // Let's peek user wallpaper first.
+ ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
+ context.getOpPackageName(), context.getAttributionTag(), this,
+ FLAG_SYSTEM, params, userId);
+ if (pfd != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options);
+ dimensions = new Rect(0, 0, options.outWidth, options.outHeight);
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "peek wallpaper dimensions failed", ex);
+ }
+ }
+ // If user wallpaper is unavailable, may be the default one instead.
+ if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
+ && returnDefault) {
+ InputStream is = openDefaultWallpaper(context, FLAG_SYSTEM);
+ if (is != null) {
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, options);
+ dimensions = new Rect(0, 0, options.outWidth, options.outHeight);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+ }
+ return dimensions;
+ }
+
void forgetLoadedWallpaper() {
synchronized (this) {
mCachedWallpaper = null;
@@ -1039,6 +1086,17 @@
}
/**
+ * Peek the dimensions of system wallpaper of the user without decoding it.
+ *
+ * @return the dimensions of system wallpaper
+ * @hide
+ */
+ public Rect peekBitmapDimensions() {
+ return sGlobals.peekWallpaperDimensions(
+ mContext, true /* returnDefault */, mContext.getUserId());
+ }
+
+ /**
* Get an open, readable file descriptor to the given wallpaper image file.
* The caller is responsible for closing the file descriptor when done ingesting the file.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0e04ad3..0fe80c4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9734,6 +9734,27 @@
}
/**
+ * @param userId The user for whom to retrieve information.
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @return Details of admin and user which enforced the restriction for the userId. If
+ * restriction is null, profile owner for the user or device owner info is returned.
+ * @hide
+ */
+ public @Nullable Bundle getEnforcingAdminAndUserDetails(int userId,
+ @Nullable String restriction) {
+ if (mService != null) {
+ try {
+ return mService.getEnforcingAdminAndUserDetails(userId, restriction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
* actual package file remain. This function can be called by a device owner, profile owner, or
* by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fdc4a16..d287437 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -251,6 +251,7 @@
boolean isNotificationListenerServicePermitted(in String packageName, int userId);
Intent createAdminSupportIntent(in String restriction);
+ Bundle getEnforcingAdminAndUserDetails(int userId,String restriction);
boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent);
boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent);
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index dae565e..67f631f 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -281,6 +281,32 @@
}
/**
+ * Convenience method for callers who need to indicate that some other package or
+ * some other user needs a backup pass. This can be useful in the case of groups of
+ * packages that share a uid and/or have user-specific data.
+ * <p>
+ * This method requires that the application hold the "android.permission.BACKUP"
+ * permission if the package named in the package argument does not run under the
+ * same uid as the caller. This method also requires that the application hold the
+ * "android.permission.INTERACT_ACROSS_USERS_FULL" if the user argument is not the
+ * same as the user the caller is running under.
+ * @param userId The user to back up
+ * @param packageName The package name identifying the application to back up.
+ *
+ * @hide
+ */
+ public static void dataChangedForUser(int userId, String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChangedForUser(userId, packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "dataChanged(userId,pkg) couldn't connect");
+ }
+ }
+ }
+
+ /**
* @deprecated Applications shouldn't request a restore operation using this method. In Android
* P and later, this method is a no-op.
*
diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java
index c64e107..f073b4e 100644
--- a/core/java/android/app/search/Query.java
+++ b/core/java/android/app/search/Query.java
@@ -70,7 +70,7 @@
@NonNull Bundle extras) {
mInput = input;
mTimestampMillis = timestampMillis;
- mExtras = extras == null ? extras : new Bundle();
+ mExtras = extras != null ? extras : new Bundle();
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d9b261f..ca5b63f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6026,6 +6026,10 @@
* more general access to the URI's content provider then this check will
* always fail.
*
+ * <strong>Note:</strong> On SDK Version {@link android.os.Build.VERSION_CODES#S},
+ * calling this method from a secondary-user's context will incorrectly return
+ * {@link PackageManager#PERMISSION_DENIED} for all {code uris}.
+ *
* @param uris The list of URIs that is being checked.
* @param pid The process ID being checked against. Must be > 0.
* @param uid The user ID being checked against. A uid of 0 is the root
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d811040..882a624 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2738,6 +2738,22 @@
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
+ /**
+ * Broadcast Action: One of the suspend conditions have been modified for the packages.
+ * <p>Includes the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been modified
+ * <li> {@link #EXTRA_CHANGED_UID_LIST} is the set of uids which have been modified
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system. It is only sent to registered receivers.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGES_SUSPENSION_CHANGED =
+ "android.intent.action.PACKAGES_SUSPENSION_CHANGED";
/**
* Broadcast Action: Distracting packages have been changed.
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 3f3db29..c8c122d 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -438,9 +438,16 @@
}
private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelAuthentication();
+ Log.d(TAG, "Cancel BP authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
@@ -853,10 +860,12 @@
* @param userId The user to authenticate
* @param operationId The keystore operation associated with authentication
*
+ * @return A requestId that can be used to cancel this operation.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void authenticateUserForOperation(
+ public long authenticateUserForOperation(
@NonNull CancellationSignal cancel,
@NonNull @CallbackExecutor Executor executor,
@NonNull AuthenticationCallback callback,
@@ -871,7 +880,8 @@
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
- authenticateInternal(operationId, cancel, executor, callback, userId);
+
+ return authenticateInternal(operationId, cancel, executor, callback, userId);
}
/**
@@ -1002,10 +1012,10 @@
authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
}
- private void cancelAuthentication() {
+ private void cancelAuthentication(long requestId) {
if (mService != null) {
try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to cancel authentication", e);
}
@@ -1024,7 +1034,7 @@
authenticateInternal(operationId, cancel, executor, callback, userId);
}
- private void authenticateInternal(
+ private long authenticateInternal(
long operationId,
@NonNull CancellationSignal cancel,
@NonNull @CallbackExecutor Executor executor,
@@ -1040,9 +1050,7 @@
try {
if (cancel.isCanceled()) {
Log.w(TAG, "Authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener());
+ return -1;
}
mExecutor = executor;
@@ -1065,14 +1073,16 @@
promptInfo = mPromptInfo;
}
- mService.authenticate(mToken, operationId, userId, mBiometricServiceReceiver,
- mContext.getOpPackageName(), promptInfo);
-
+ final long authId = mService.authenticate(mToken, operationId, userId,
+ mBiometricServiceReceiver, mContext.getOpPackageName(), promptInfo);
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ return authId;
} catch (RemoteException e) {
Log.e(TAG, "Remote exception while authenticating", e);
mExecutor.execute(() -> callback.onAuthenticationError(
BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
mContext.getString(R.string.biometric_error_hw_unavailable)));
+ return -1;
}
}
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 4c2a9ae..91f794c 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -41,13 +41,14 @@
// Retrieve the package where BIometricOrompt's UI is implemented
String getUiPackage();
- // Requests authentication. The service choose the appropriate biometric to use, and show
- // the corresponding BiometricDialog.
- void authenticate(IBinder token, long sessionId, int userId,
+ // Requests authentication. The service chooses the appropriate biometric to use, and shows
+ // the corresponding BiometricDialog. A requestId is returned that can be used to cancel
+ // this operation.
+ long authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, in PromptInfo promptInfo);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
// TODO(b/141025588): Make userId the first arg to be consistent with hasEnrolledBiometrics.
// Checks if biometrics can be used.
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 876513f..addd622 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -48,13 +48,13 @@
// startPreparedClient().
void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication);
+ long requestId, int cookie, boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int cookie);
- // Cancels authentication.
- void cancelAuthenticationFromService(IBinder token, String opPackageName);
+ // Cancels authentication for the given requestId.
+ void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId);
// Determine if HAL is loaded and ready
boolean isHardwareDetected(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 64b5118..2c3c8c3 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -36,13 +36,14 @@
// Retrieve static sensor properties for all biometric sensors
List<SensorPropertiesInternal> getSensorProperties(String opPackageName);
- // Requests authentication. The service choose the appropriate biometric to use, and show
- // the corresponding BiometricDialog.
- void authenticate(IBinder token, long operationId, int userId,
+ // Requests authentication. The service chooses the appropriate biometric to use, and shows
+ // the corresponding BiometricDialog. A requestId is returned that can be used to cancel
+ // this operation.
+ long authenticate(IBinder token, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, in PromptInfo promptInfo);
- // Cancel authentication for the given session.
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
// Checks if biometrics can be used.
int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators);
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 5cfba3d..395c655 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -263,12 +263,12 @@
@Override
public void onServiceConnected(ComponentName component, IBinder binder) {
mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
- mInitFuture.setStatus(true);
try {
mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
} catch (RemoteException e) {
Log.e(TAG, "Remote IPC failed!");
}
+ mInitFuture.setStatus(true);
}
};
ctx.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT |
diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java
index 20ca4a3..032ed7e 100644
--- a/core/java/android/hardware/camera2/CaptureFailure.java
+++ b/core/java/android/hardware/camera2/CaptureFailure.java
@@ -59,7 +59,7 @@
private final CaptureRequest mRequest;
private final int mReason;
- private final boolean mDropped;
+ private final boolean mWasImageCaptured;
private final int mSequenceId;
private final long mFrameNumber;
private final String mErrorPhysicalCameraId;
@@ -68,10 +68,11 @@
* @hide
*/
public CaptureFailure(CaptureRequest request, int reason,
- boolean dropped, int sequenceId, long frameNumber, String errorPhysicalCameraId) {
+ boolean wasImageCaptured, int sequenceId, long frameNumber,
+ String errorPhysicalCameraId) {
mRequest = request;
mReason = reason;
- mDropped = dropped;
+ mWasImageCaptured = wasImageCaptured;
mSequenceId = sequenceId;
mFrameNumber = frameNumber;
mErrorPhysicalCameraId = errorPhysicalCameraId;
@@ -141,7 +142,7 @@
* @return boolean True if the image was captured, false otherwise.
*/
public boolean wasImageCaptured() {
- return !mDropped;
+ return mWasImageCaptured;
}
/**
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 8da6551..b8443fb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -873,21 +873,19 @@
@Override
public int submitBurst(List<Request> requests, IRequestCallback callback) {
int seqId = -1;
- synchronized (mInterfaceLock) {
- try {
- CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
- ArrayList<CaptureRequest> captureRequests = new ArrayList<>();
- for (Request request : requests) {
- captureRequests.add(initializeCaptureRequest(mCameraDevice, request,
- mCameraConfigMap));
- }
- seqId = mCaptureSession.captureBurstRequests(captureRequests,
- new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed to submit capture requests!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
+ try {
+ CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
+ ArrayList<CaptureRequest> captureRequests = new ArrayList<>();
+ for (Request request : requests) {
+ captureRequests.add(initializeCaptureRequest(mCameraDevice, request,
+ mCameraConfigMap));
}
+ seqId = mCaptureSession.captureBurstRequests(captureRequests,
+ new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to submit capture requests!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
return seqId;
@@ -896,18 +894,16 @@
@Override
public int setRepeating(Request request, IRequestCallback callback) {
int seqId = -1;
- synchronized (mInterfaceLock) {
- try {
- CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice,
- request, mCameraConfigMap);
- CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
- seqId = mCaptureSession.setSingleRepeatingRequest(repeatingRequest,
- new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed to enable repeating request!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice,
+ request, mCameraConfigMap);
+ CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
+ seqId = mCaptureSession.setSingleRepeatingRequest(repeatingRequest,
+ new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to enable repeating request!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
return seqId;
@@ -915,27 +911,23 @@
@Override
public void abortCaptures() {
- synchronized (mInterfaceLock) {
- try {
- mCaptureSession.abortCaptures();
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed during capture abort!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ mCaptureSession.abortCaptures();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed during capture abort!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
}
@Override
public void stopRepeating() {
- synchronized (mInterfaceLock) {
- try {
- mCaptureSession.stopRepeating();
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed during repeating capture stop!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ mCaptureSession.stopRepeating();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed during repeating capture stop!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index fc728a2..8864939 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -104,6 +104,9 @@
private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
new SparseArray<CaptureCallbackHolder>();
+ /** map request IDs which have batchedOutputs to requestCount*/
+ private HashMap<Integer, Integer> mBatchOutputMap = new HashMap<>();
+
private int mRepeatingRequestId = REQUEST_ID_NONE;
// Latest repeating request list's types
private int[] mRepeatingRequestTypes;
@@ -973,6 +976,7 @@
mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(REQUEST_ID_NONE, null);
mIdle = true;
mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>();
+ mBatchOutputMap = new HashMap<>();
mFrameNumberTracker = new FrameNumberTracker();
mCurrentSession.closeWithoutDraining();
@@ -1179,6 +1183,41 @@
return requestTypes;
}
+ private boolean hasBatchedOutputs(List<CaptureRequest> requestList) {
+ boolean hasBatchedOutputs = true;
+ for (int i = 0; i < requestList.size(); i++) {
+ CaptureRequest request = requestList.get(i);
+ if (!request.isPartOfCRequestList()) {
+ hasBatchedOutputs = false;
+ break;
+ }
+ if (i == 0) {
+ Collection<Surface> targets = request.getTargets();
+ if (targets.size() != 2) {
+ hasBatchedOutputs = false;
+ break;
+ }
+ }
+ }
+ return hasBatchedOutputs;
+ }
+
+ private void updateTracker(int requestId, long frameNumber,
+ int requestType, CaptureResult result, boolean isPartialResult) {
+ int requestCount = 1;
+ // If the request has batchedOutputs update each frame within the batch.
+ if (mBatchOutputMap.containsKey(requestId)) {
+ requestCount = mBatchOutputMap.get(requestId);
+ for (int i = 0; i < requestCount; i++) {
+ mFrameNumberTracker.updateTracker(frameNumber - (requestCount - 1 - i),
+ result, isPartialResult, requestType);
+ }
+ } else {
+ mFrameNumberTracker.updateTracker(frameNumber, result,
+ isPartialResult, requestType);
+ }
+ }
+
private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,
Executor executor, boolean repeating) throws CameraAccessException {
@@ -1224,6 +1263,14 @@
request.recoverStreamIdToSurface();
}
+ // If the request has batched outputs, then store the
+ // requestCount and requestId in the map.
+ boolean hasBatchedOutputs = hasBatchedOutputs(requestList);
+ if (hasBatchedOutputs) {
+ int requestCount = requestList.size();
+ mBatchOutputMap.put(requestInfo.getRequestId(), requestCount);
+ }
+
if (callback != null) {
mCaptureCallbackMap.put(requestInfo.getRequestId(),
new CaptureCallbackHolder(
@@ -1820,7 +1867,7 @@
final CaptureFailure failure = new CaptureFailure(
request,
reason,
- /*dropped*/ mayHaveBuffers,
+ mayHaveBuffers,
requestId,
frameNumber,
errorPhysicalCameraId);
@@ -1839,8 +1886,18 @@
if (DEBUG) {
Log.v(TAG, String.format("got error frame %d", frameNumber));
}
- mFrameNumberTracker.updateTracker(frameNumber,
- /*error*/true, request.getRequestType());
+
+ // Update FrameNumberTracker for every frame during HFR mode.
+ if (mBatchOutputMap.containsKey(requestId)) {
+ for (int i = 0; i < mBatchOutputMap.get(requestId); i++) {
+ mFrameNumberTracker.updateTracker(frameNumber - (subsequenceId - i),
+ /*error*/true, request.getRequestType());
+ }
+ } else {
+ mFrameNumberTracker.updateTracker(frameNumber,
+ /*error*/true, request.getRequestType());
+ }
+
checkAndFireSequenceComplete();
// Dispatch the failure callback
@@ -2023,7 +2080,6 @@
public void onResultReceived(CameraMetadataNative result,
CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
throws RemoteException {
-
int requestId = resultExtras.getRequestId();
long frameNumber = resultExtras.getFrameNumber();
@@ -2064,8 +2120,8 @@
+ frameNumber);
}
- mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, /*result*/null,
+ isPartialResult);
return;
}
@@ -2077,8 +2133,9 @@
+ frameNumber);
}
- mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, /*result*/null,
+ isPartialResult);
+
return;
}
@@ -2184,9 +2241,7 @@
Binder.restoreCallingIdentity(ident);
}
- // Collect the partials for a total result; or mark the frame as totally completed
- mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, finalResult, isPartialResult);
// Fire onCaptureSequenceCompleted
if (!isPartialResult) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 3b1cb94..425f22c 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -58,7 +58,7 @@
private static final class JpegParameters {
public HashSet<Long> mTimeStamps = new HashSet<>();
- public int mRotation = JPEG_DEFAULT_ROTATION; // CCW multiple of 90 degrees
+ public int mRotation = JPEG_DEFAULT_ROTATION; // CW multiple of 90 degrees
public int mQuality = JPEG_DEFAULT_QUALITY; // [0..100]
}
@@ -100,7 +100,8 @@
Integer orientation = captureBundles.get(0).captureResult.get(
CaptureResult.JPEG_ORIENTATION);
if (orientation != null) {
- ret.mRotation = orientation / 90;
+ // The jpeg encoder expects CCW rotation, convert from CW
+ ret.mRotation = (360 - (orientation % 360)) / 90;
} else {
Log.w(TAG, "No jpeg rotation set, using default: " + JPEG_DEFAULT_ROTATION);
}
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index c5d37c2..0dc8f92 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -60,12 +60,18 @@
/** Brightness */
public final float brightness;
+ /** Brightness after {@link DisplayPowerController} adjustments */
+ public final float adjustedBrightness;
+
/** Current minimum supported brightness. */
public final float brightnessMinimum;
/** Current maximum supported brightness. */
public final float brightnessMaximum;
+ /** Brightness values greater than this point are only used in High Brightness Mode. */
+ public final float highBrightnessTransitionPoint;
+
/**
* Current state of high brightness mode.
* Can be any of HIGH_BRIGHTNESS_MODE_* values.
@@ -73,11 +79,20 @@
public final int highBrightnessMode;
public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
- @HighBrightnessMode int highBrightnessMode) {
+ @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint) {
+ this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode,
+ highBrightnessTransitionPoint);
+ }
+
+ public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum,
+ float brightnessMaximum, @HighBrightnessMode int highBrightnessMode,
+ float highBrightnessTransitionPoint) {
this.brightness = brightness;
+ this.adjustedBrightness = adjustedBrightness;
this.brightnessMinimum = brightnessMinimum;
this.brightnessMaximum = brightnessMaximum;
this.highBrightnessMode = highBrightnessMode;
+ this.highBrightnessTransitionPoint = highBrightnessTransitionPoint;
}
/**
@@ -103,9 +118,11 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(brightness);
+ dest.writeFloat(adjustedBrightness);
dest.writeFloat(brightnessMinimum);
dest.writeFloat(brightnessMaximum);
dest.writeInt(highBrightnessMode);
+ dest.writeFloat(highBrightnessTransitionPoint);
}
public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
@@ -123,9 +140,11 @@
private BrightnessInfo(Parcel source) {
brightness = source.readFloat();
+ adjustedBrightness = source.readFloat();
brightnessMinimum = source.readFloat();
brightnessMaximum = source.readFloat();
highBrightnessMode = source.readInt();
+ highBrightnessTransitionPoint = source.readFloat();
}
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a9b95fc..6c39365 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -361,6 +361,11 @@
for (int i = 0; i < numListeners; i++) {
mask |= mDisplayListeners.get(i).mEventsMask;
}
+ if (mDispatchNativeCallbacks) {
+ mask |= DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ }
return mask;
}
@@ -1047,12 +1052,17 @@
private static native void nSignalNativeCallbacks(float refreshRate);
- // Called from AChoreographer via JNI.
- // Registers AChoreographer so that refresh rate callbacks can be dispatched from DMS.
- private void registerNativeChoreographerForRefreshRateCallbacks() {
+ /**
+ * Called from AChoreographer via JNI.
+ * Registers AChoreographer so that refresh rate callbacks can be dispatched from DMS.
+ * Public for unit testing to be able to call this method.
+ */
+ @VisibleForTesting
+ public void registerNativeChoreographerForRefreshRateCallbacks() {
synchronized (mLock) {
- registerCallbackIfNeededLocked();
mDispatchNativeCallbacks = true;
+ registerCallbackIfNeededLocked();
+ updateCallbackIfNeededLocked();
DisplayInfo display = getDisplayInfoLocked(Display.DEFAULT_DISPLAY);
if (display != null) {
// We need to tell AChoreographer instances the current refresh rate so that apps
@@ -1063,11 +1073,16 @@
}
}
- // Called from AChoreographer via JNI.
- // Unregisters AChoreographer from receiving refresh rate callbacks.
- private void unregisterNativeChoreographerForRefreshRateCallbacks() {
+ /**
+ * Called from AChoreographer via JNI.
+ * Unregisters AChoreographer from receiving refresh rate callbacks.
+ * Public for unit testing to be able to call this method.
+ */
+ @VisibleForTesting
+ public void unregisterNativeChoreographerForRefreshRateCallbacks() {
synchronized (mLock) {
mDispatchNativeCallbacks = false;
+ updateCallbackIfNeededLocked();
}
}
}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 385ad2d..56f8142 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -58,7 +58,7 @@
public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
private static final String TAG = "FaceManager";
- private static final boolean DEBUG = true;
+
private static final int MSG_ENROLL_RESULT = 100;
private static final int MSG_ACQUIRED = 101;
private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -207,13 +207,9 @@
throw new IllegalArgumentException("Must supply an authentication callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "authentication already canceled");
+ return;
}
if (mService != null) {
@@ -223,17 +219,18 @@
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
- mService.authenticate(mToken, operationId, userId, mServiceReceiver,
- mContext.getOpPackageName(), isKeyguardBypassEnabled);
+ final long authId = mService.authenticate(mToken, operationId, userId,
+ mServiceReceiver, mContext.getOpPackageName(), isKeyguardBypassEnabled);
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception while authenticating: ", e);
- if (callback != null) {
- // Though this may not be a hardware issue, it will cause apps to give up or
- // try again later.
- callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
- getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */));
- }
+ // Though this may not be a hardware issue, it will cause apps to give up or
+ // try again later.
+ callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
+ getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */));
} finally {
Trace.endSection();
}
@@ -255,14 +252,14 @@
if (cancel.isCanceled()) {
Slog.w(TAG, "Detection already cancelled");
return;
- } else {
- cancel.setOnCancelListener(new OnFaceDetectionCancelListener());
}
mFaceDetectionCallback = callback;
try {
- mService.detectFace(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final long authId = mService.detectFace(
+ mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
}
@@ -726,23 +723,23 @@
}
}
- private void cancelAuthentication(CryptoObject cryptoObject) {
+ private void cancelAuthentication(long requestId) {
if (mService != null) {
try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
- private void cancelFaceDetect() {
+ private void cancelFaceDetect(long requestId) {
if (mService == null) {
return;
}
try {
- mService.cancelFaceDetect(mToken, mContext.getOpPackageName());
+ mService.cancelFaceDetect(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -794,9 +791,9 @@
// This is used as a last resort in case a vendor string is missing
// It should not happen for anything other than FACE_ERROR_VENDOR, but
// warn and use the default if all else fails.
- // TODO(b/196639965): update string
Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
- return "";
+ return context.getString(
+ com.android.internal.R.string.face_error_vendor_unknown);
}
/**
@@ -1110,22 +1107,30 @@
}
private class OnAuthenticationCancelListener implements OnCancelListener {
- private final CryptoObject mCrypto;
+ private final long mAuthRequestId;
- OnAuthenticationCancelListener(CryptoObject crypto) {
- mCrypto = crypto;
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
}
@Override
public void onCancel() {
- cancelAuthentication(mCrypto);
+ Slog.d(TAG, "Cancel face authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
private class OnFaceDetectionCancelListener implements OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnFaceDetectionCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelFaceDetect();
+ Slog.d(TAG, "Cancel face detect requested for: " + mAuthRequestId);
+ cancelFaceDetect(mAuthRequestId);
}
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index db02a0ef..e919824 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -44,34 +44,36 @@
// Retrieve static sensor properties for the specified sensor
FaceSensorPropertiesInternal getSensorProperties(int sensorId, String opPackageName);
- // Authenticate the given sessionId with a face
- void authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
+ // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
+ long authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
String opPackageName, boolean isKeyguardBypassEnabled);
// Uses the face hardware to detect for the presence of a face, without giving details
- // about accept/reject/lockout.
- void detectFace(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
+ // about accept/reject/lockout. A requestId is returned that can be used to cancel this
+ // operation.
+ long detectFace(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
// This method prepares the service to start authenticating, but doesn't start authentication.
// This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
// called from BiometricService. The additional uid, pid, userId arguments should be determined
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
- void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token, long operationId,
- int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication);
+ void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token,
+ long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
+ String opPackageName, long requestId, int cookie,
+ boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int sensorId, int cookie);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
- // Cancel face detection
- void cancelFaceDetect(IBinder token, String opPackageName);
+ // Cancel face detection for the given requestId.
+ void cancelFaceDetect(IBinder token, String opPackageName, long requestId);
// Same as above, with extra arguments.
- void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName);
+ void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start face enrollment
void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 87d45b9..a3d595c 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -146,6 +146,7 @@
private CryptoObject mCryptoObject;
@Nullable private RemoveTracker mRemoveTracker;
private Handler mHandler;
+ @Nullable private float[] mEnrollStageThresholds;
/**
* Retrieves a list of properties for all fingerprint sensors on the device.
@@ -189,22 +190,30 @@
}
private class OnAuthenticationCancelListener implements OnCancelListener {
- private android.hardware.biometrics.CryptoObject mCrypto;
+ private final long mAuthRequestId;
- public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) {
- mCrypto = crypto;
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
}
@Override
public void onCancel() {
- cancelAuthentication(mCrypto);
+ Slog.d(TAG, "Cancel fingerprint authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
private class OnFingerprintDetectionCancelListener implements OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnFingerprintDetectionCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelFingerprintDetect();
+ Slog.d(TAG, "Cancel fingerprint detect requested for: " + mAuthRequestId);
+ cancelFingerprintDetect(mAuthRequestId);
}
}
@@ -552,13 +561,9 @@
throw new IllegalArgumentException("Must supply an authentication callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "authentication already canceled");
+ return;
}
if (mService != null) {
@@ -567,8 +572,11 @@
mAuthenticationCallback = callback;
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
- mService.authenticate(mToken, operationId, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final long authId = mService.authenticate(mToken, operationId, sensorId, userId,
+ mServiceReceiver, mContext.getOpPackageName());
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception while authenticating: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or try
@@ -595,15 +603,14 @@
if (cancel.isCanceled()) {
Slog.w(TAG, "Detection already cancelled");
return;
- } else {
- cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener());
}
mFingerprintDetectionCallback = callback;
try {
- mService.detectFingerprint(mToken, userId, mServiceReceiver,
+ final long authId = mService.detectFingerprint(mToken, userId, mServiceReceiver,
mContext.getOpPackageName());
+ cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
}
@@ -844,26 +851,6 @@
}
/**
- * Checks if the specified user has enrollments in any of the specified sensors.
- * @hide
- */
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public boolean hasEnrolledTemplatesForAnySensor(int userId,
- @NonNull List<FingerprintSensorPropertiesInternal> sensors) {
- if (mService == null) {
- Slog.w(TAG, "hasEnrolledTemplatesForAnySensor: no fingerprint service");
- return false;
- }
-
- try {
- return mService.hasEnrolledTemplatesForAnySensor(userId, sensors,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1320,21 +1307,21 @@
}
}
- private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) {
+ private void cancelAuthentication(long requestId) {
if (mService != null) try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- private void cancelFingerprintDetect() {
+ private void cancelFingerprintDetect(long requestId) {
if (mService == null) {
return;
}
try {
- mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName());
+ mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1343,6 +1330,46 @@
/**
* @hide
*/
+ public int getEnrollStageCount() {
+ if (mEnrollStageThresholds == null) {
+ mEnrollStageThresholds = createEnrollStageThresholds(mContext);
+ }
+ return mEnrollStageThresholds.length + 1;
+ }
+
+ /**
+ * @hide
+ */
+ public float getEnrollStageThreshold(int index) {
+ if (mEnrollStageThresholds == null) {
+ mEnrollStageThresholds = createEnrollStageThresholds(mContext);
+ }
+
+ if (index < 0 || index > mEnrollStageThresholds.length) {
+ Slog.w(TAG, "Unsupported enroll stage index: " + index);
+ return index < 0 ? 0f : 1f;
+ }
+
+ // The implicit threshold for the final stage is always 1.
+ return index == mEnrollStageThresholds.length ? 1f : mEnrollStageThresholds[index];
+ }
+
+ @NonNull
+ private static float[] createEnrollStageThresholds(@NonNull Context context) {
+ // TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config
+ final String[] enrollStageThresholdStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.config_udfps_enroll_stage_thresholds);
+
+ final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length];
+ for (int i = 0; i < enrollStageThresholds.length; i++) {
+ enrollStageThresholds[i] = Float.parseFloat(enrollStageThresholdStrings[i]);
+ }
+ return enrollStageThresholds;
+ }
+
+ /**
+ * @hide
+ */
public static String getErrorString(Context context, int errMsg, int vendorCode) {
switch (errMsg) {
case FINGERPRINT_ERROR_HW_UNAVAILABLE:
@@ -1390,9 +1417,9 @@
// This is used as a last resort in case a vendor string is missing
// It should not happen for anything other than FINGERPRINT_ERROR_VENDOR, but
// warn and use the default if all else fails.
- // TODO(b/196639965): update string
Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
- return "";
+ return context.getString(
+ com.android.internal.R.string.fingerprint_error_vendor_unknown);
}
/**
diff --git a/core/java/android/hardware/fingerprint/FingerprintStateListener.java b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
index 6e607a2..cf914c5 100644
--- a/core/java/android/hardware/fingerprint/FingerprintStateListener.java
+++ b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
@@ -49,5 +49,10 @@
* Defines behavior in response to state update
* @param newState new state of fingerprint sensor
*/
- public abstract void onStateChanged(@FingerprintStateListener.State int newState);
+ public void onStateChanged(@FingerprintStateListener.State int newState) {};
+
+ /**
+ * Invoked when enrollment state changes for the specified user
+ */
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {};
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 3979afe..de94b2f 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -48,15 +48,16 @@
// Retrieve static sensor properties for the specified sensor
FingerprintSensorPropertiesInternal getSensorProperties(int sensorId, String opPackageName);
- // Authenticate the given sessionId with a fingerprint. This is protected by
- // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes
- // through FingerprintManager now.
- void authenticate(IBinder token, long operationId, int sensorId, int userId,
+ // Authenticate with a fingerprint. This is protected by USE_FINGERPRINT/USE_BIOMETRIC
+ // permission. This is effectively deprecated, since it only comes through FingerprintManager
+ // now. A requestId is returned that can be used to cancel this operation.
+ long authenticate(IBinder token, long operationId, int sensorId, int userId,
IFingerprintServiceReceiver receiver, String opPackageName);
// Uses the fingerprint hardware to detect for the presence of a finger, without giving details
- // about accept/reject/lockout.
- void detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
+ // about accept/reject/lockout. A requestId is returned that can be used to cancel this
+ // operation.
+ long detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
String opPackageName);
// This method prepares the service to start authenticating, but doesn't start authentication.
@@ -65,21 +66,21 @@
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId,
- IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie,
- boolean allowBackgroundAuthentication);
+ IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+ int cookie, boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int sensorId, int cookie);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
- // Cancel finger detection
- void cancelFingerprintDetect(IBinder token, String opPackageName);
+ // Cancel finger detection for the given requestId.
+ void cancelFingerprintDetect(IBinder token, String opPackageName, long requestId);
// Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
// an additional uid, pid, userid.
- void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName);
+ void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start fingerprint enrollment
void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
@@ -119,9 +120,6 @@
// Determine if a user has at least one enrolled fingerprint.
boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName);
- // Determine if a user has at least one enrolled fingerprint in any of the specified sensors
- boolean hasEnrolledTemplatesForAnySensor(int userId, in List<FingerprintSensorPropertiesInternal> sensors, String opPackageName);
-
// Return the LockoutTracker status for the specified user
int getLockoutModeForUser(int sensorId, int userId);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
index 56dba7e..1aa6fa1 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
@@ -24,4 +24,5 @@
*/
oneway interface IFingerprintStateListener {
void onStateChanged(int newState);
+ void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments);
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 881e0cf..74cb42d 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1749,12 +1749,12 @@
if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) {
return false;
}
- if ((mInputEditorInfo != null
- && (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0)
+ if (mInputEditorInfo != null
+ && ((mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0
// If app window has portrait orientation, regardless of what display orientation
// is, IME shouldn't use fullscreen-mode.
|| (mInputEditorInfo.internalImeOptions
- & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0) {
+ & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0)) {
return false;
}
return true;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index f483752..0f94cbe 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -271,6 +271,16 @@
}
/**
+ * Returns the names of custom power components in order, so the first name in the array
+ * corresponds to the custom componentId
+ * {@link BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID}.
+ */
+ @NonNull
+ public String[] getCustomPowerComponentNames() {
+ return mCustomPowerComponentNames;
+ }
+
+ /**
* Returns an iterator for {@link android.os.BatteryStats.HistoryItem}'s.
*/
@NonNull
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 3d466a0..c646623 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -74,7 +74,7 @@
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
// Debuggable builds will throw an AssertionError if the number of map entries exceeds:
- private static final int CRASH_AT_SIZE = 20_000;
+ private static final int CRASH_AT_SIZE = 25_000;
/**
* We next warn when we exceed this bucket size.
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index be21fea..1cceea3 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -17,6 +17,7 @@
package android.os;
import android.app.Activity;
+import android.app.GameManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,8 +27,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -37,9 +36,6 @@
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
@@ -88,9 +84,6 @@
private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
- // ANGLE related properties.
- private static final String ANGLE_RULES_FILE = "a4a_rules.json";
- private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
"android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
@@ -121,6 +114,7 @@
private ClassLoader mClassLoader;
private String mLibrarySearchPaths;
private String mLibraryPermittedPaths;
+ private GameManager mGameManager;
private int mAngleOptInIndex = -1;
@@ -133,6 +127,8 @@
final ApplicationInfo appInfoWithMetaData =
getAppInfoWithMetadata(context, pm, packageName);
+ mGameManager = context.getSystemService(GameManager.class);
+
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -151,6 +147,23 @@
}
/**
+ * Query to determine if the Game Mode has enabled ANGLE.
+ */
+ private boolean isAngleEnabledByGameMode(Context context, String packageName) {
+ try {
+ final boolean gameModeEnabledAngle =
+ (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
+ Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
+ return gameModeEnabledAngle;
+ } catch (SecurityException e) {
+ Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled "
+ + "for package: " + packageName);
+ }
+
+ return false;
+ }
+
+ /**
* Query to determine if ANGLE should be used
*/
private boolean shouldUseAngle(Context context, Bundle coreSettings,
@@ -164,21 +177,16 @@
Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
+ "set to: '" + devOptIn + "'");
- // We only want to use ANGLE if the app is in the allowlist, or the developer has
- // explicitly chosen something other than default driver.
- // The allowlist will be generated by the ANGLE APK at both boot time and
- // ANGLE update time. It will only include apps mentioned in the rules file.
- final boolean allowed = checkAngleAllowlist(context, coreSettings, packageName);
+ // We only want to use ANGLE if the developer has explicitly chosen something other than
+ // default driver.
final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
-
- if (allowed) {
- Log.v(TAG, "ANGLE allowlist includes " + packageName);
- }
if (requested) {
Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
}
- return allowed || requested;
+ final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
+
+ return requested || gameModeEnabledAngle;
}
private int getVulkanVersion(PackageManager pm) {
@@ -475,117 +483,6 @@
}
/**
- * Attempt to setup ANGLE with a temporary rules file.
- * True: Temporary rules file was loaded.
- * False: Temporary rules file was *not* loaded.
- */
- private boolean setupAngleWithTempRulesFile(Context context,
- String packageName,
- String paths,
- String devOptIn) {
- /**
- * We only want to load a temp rules file for:
- * - apps that are marked 'debuggable' in their manifest
- * - devices that are running a userdebug build (ro.debuggable) or can inject libraries for
- * debugging (PR_SET_DUMPABLE).
- */
- if (!isDebuggable()) {
- Log.v(TAG, "Skipping loading temporary rules file");
- return false;
- }
-
- final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES);
-
- if (TextUtils.isEmpty(angleTempRules)) {
- Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty");
- return false;
- }
-
- Log.i(TAG, "Detected system property " + ANGLE_TEMP_RULES + ": " + angleTempRules);
-
- final File tempRulesFile = new File(angleTempRules);
- if (tempRulesFile.exists()) {
- Log.i(TAG, angleTempRules + " exists, loading file.");
- try {
- final FileInputStream stream = new FileInputStream(angleTempRules);
-
- try {
- final FileDescriptor rulesFd = stream.getFD();
- final long rulesOffset = 0;
- final long rulesLength = stream.getChannel().size();
- Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules);
-
- setAngleInfo(paths, packageName, devOptIn, null,
- rulesFd, rulesOffset, rulesLength);
-
- stream.close();
-
- // We successfully setup ANGLE, so return with good status
- return true;
- } catch (IOException e) {
- Log.w(TAG, "Hit IOException thrown by FileInputStream: " + e);
- }
- } catch (FileNotFoundException e) {
- Log.w(TAG, "Temp ANGLE rules file not found: " + e);
- } catch (SecurityException e) {
- Log.w(TAG, "Temp ANGLE rules file not accessible: " + e);
- }
- }
-
- return false;
- }
-
- /**
- * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
- * True: APK rules file was loaded.
- * False: APK rules file was *not* loaded.
- */
- private boolean setupAngleRulesApk(String anglePkgName,
- ApplicationInfo angleInfo,
- PackageManager pm,
- String packageName,
- String paths,
- String devOptIn,
- String[] features) {
- // Pass the rules file to loader for ANGLE decisions
- try {
- final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets();
-
- try {
- final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE);
-
- setAngleInfo(paths, packageName, devOptIn, features, assetsFd.getFileDescriptor(),
- assetsFd.getStartOffset(), assetsFd.getLength());
-
- assetsFd.close();
-
- return true;
- } catch (IOException e) {
- Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE
- + " from '" + anglePkgName + "': " + e);
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "': " + e);
- }
-
- return false;
- }
-
- /**
- * Pull ANGLE allowlist from GlobalSettings and compare against current package
- */
- private boolean checkAngleAllowlist(Context context, Bundle bundle, String packageName) {
- final ContentResolver contentResolver = context.getContentResolver();
- final List<String> angleAllowlist =
- getGlobalSettingsString(contentResolver, bundle,
- Settings.Global.ANGLE_ALLOWLIST);
-
- if (DEBUG) Log.v(TAG, "ANGLE allowlist: " + angleAllowlist);
-
- return angleAllowlist.contains(packageName);
- }
-
- /**
* Pass ANGLE details down to trigger enable logic
*
* @param context
@@ -647,28 +544,21 @@
if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths);
- // If the user has set the developer option to something other than default,
- // we need to call setupAngleRulesApk() with the package name and the developer
- // option value (native/angle/other). Then later when we are actually trying to
- // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
- // and can confidently answer yes/no based on the previously set developer
- // option value.
- final String devOptIn = getDriverForPackage(context, bundle, packageName);
-
- if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) {
- // We setup ANGLE with a temp rules file, so we're done here.
- return true;
+ // We need to call setAngleInfo() with the package name and the developer option value
+ //(native/angle/other). Then later when we are actually trying to load a driver,
+ //GraphicsEnv::getShouldUseAngle() has seen the package name before and can confidently
+ //answer yes/no based on the previously set developer option value.
+ final String devOptIn;
+ final String[] features = getAngleEglFeatures(context, bundle);
+ final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
+ if (gameModeEnabledAngle) {
+ devOptIn = ANGLE_GL_DRIVER_CHOICE_ANGLE;
+ } else {
+ devOptIn = getDriverForPackage(context, bundle, packageName);
}
+ setAngleInfo(paths, packageName, devOptIn, features);
- String[] features = getAngleEglFeatures(context, bundle);
-
- if (setupAngleRulesApk(
- anglePkgName, angleInfo, pm, packageName, paths, devOptIn, features)) {
- // ANGLE with rules is set up from the APK, hence return.
- return true;
- }
-
- return false;
+ return true;
}
/**
@@ -956,7 +846,7 @@
private static native void setGpuStats(String driverPackageName, String driverVersionName,
long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
private static native void setAngleInfo(String path, String appPackage, String devOptIn,
- String[] features, FileDescriptor rulesFd, long rulesOffset, long rulesLength);
+ String[] features);
private static native boolean getShouldUseAngle(String packageName);
private static native boolean setInjectLayersPrSetDumpable();
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 54905ec..8928a42 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.IVold;
import java.util.List;
@@ -135,4 +136,19 @@
* {@link VolumeInfo#isPrimary()}
*/
public abstract List<String> getPrimaryVolumeIds();
+
+ /**
+ * Tells StorageManager that CE storage for this user has been prepared.
+ *
+ * @param userId userId for which CE storage has been prepared
+ */
+ public abstract void markCeStoragePrepared(@UserIdInt int userId);
+
+ /**
+ * Returns true when CE storage for this user has been prepared.
+ *
+ * When the user key is unlocked and CE storage has been prepared,
+ * it's ok to access and modify CE directories on volumes for this user.
+ */
+ public abstract boolean isCeStoragePrepared(@UserIdInt int userId);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ac520e8..00abc75 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2242,6 +2242,21 @@
public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
/**
+ * Activity Action: Show screen that lets user configure wifi tethering.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * Input: Nothing
+ * <p>
+ * Output: Nothing
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_WIFI_TETHER_SETTING =
+ "com.android.settings.WIFI_TETHER_SETTINGS";
+
+ /**
* Broadcast to trigger notification of asking user to enable MMS.
* Need to specify {@link #EXTRA_ENABLE_MMS_DATA_REQUEST_REASON} and {@link #EXTRA_SUB_ID}.
*
@@ -11845,6 +11860,12 @@
public static final String WIFI_MIGRATION_COMPLETED = "wifi_migration_completed";
/**
+ * Whether UWB should be enabled.
+ * @hide
+ */
+ public static final String UWB_ENABLED = "uwb_enabled";
+
+ /**
* Value to specify whether network quality scores and badging should be shown in the UI.
*
* Type: int (0 for false, 1 for true)
@@ -13670,13 +13691,6 @@
"angle_gl_driver_selection_values";
/**
- * List of package names that should check ANGLE rules
- * @hide
- */
- @Readable
- public static final String ANGLE_ALLOWLIST = "angle_allowlist";
-
- /**
* Lists of ANGLE EGL features for debugging.
* Each list of features is separated by a comma, each feature in each list is separated by
* a colon.
@@ -14915,6 +14929,16 @@
"power_button_long_press";
/**
+ * Override internal R.integer.config_longPressOnPowerDurationMs. It determines the length
+ * of power button press to be considered a long press in milliseconds.
+ * Used by PhoneWindowManager.
+ * @hide
+ */
+ @Readable
+ public static final String POWER_BUTTON_LONG_PRESS_DURATION_MS =
+ "power_button_long_press_duration_ms";
+
+ /**
* Overrides internal R.integer.config_veryLongPressOnPowerBehavior.
* Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
* Used by PhoneWindowManager.
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 8e4a68e..4004148 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -436,7 +436,7 @@
try {
ApplicationInfo ai = context.getPackageManager()
.getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
- getUserId());
+ getNormalizedUserId());
mContext = context.createApplicationContext(ai,
Context.CONTEXT_RESTRICTED);
} catch (PackageManager.NameNotFoundException e) {
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f477072..c9a0121 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -96,6 +96,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -152,6 +153,7 @@
private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private static final int MSG_ZOOM = 10100;
private static final int MSG_SCALE_PREVIEW = 10110;
+ private static final int MSG_REPORT_SHOWN = 10150;
private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
Float.NEGATIVE_INFINITY);
@@ -527,6 +529,39 @@
}
/**
+ * This will be called in the end of {@link #updateSurface(boolean, boolean, boolean)}.
+ * If true is returned, the engine will not report shown until rendering finished is
+ * reported. Otherwise, the engine will report shown immediately right after redraw phase
+ * in {@link #updateSurface(boolean, boolean, boolean)}.
+ *
+ * @hide
+ */
+ public boolean shouldWaitForEngineShown() {
+ return false;
+ }
+
+ /**
+ * Reports the rendering is finished, stops waiting, then invokes
+ * {@link IWallpaperEngineWrapper#reportShown()}.
+ *
+ * @hide
+ */
+ public void reportEngineShown(boolean waitForEngineShown) {
+ if (mIWallpaperEngine.mShownReported) return;
+ if (!waitForEngineShown) {
+ Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
+ mCaller.removeMessages(MSG_REPORT_SHOWN);
+ mCaller.sendMessage(message);
+ } else {
+ // if we are already waiting, no need to reset the timeout.
+ if (!mCaller.hasMessages(MSG_REPORT_SHOWN)) {
+ Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
+ mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5));
+ }
+ }
+ }
+
+ /**
* Control whether this wallpaper will receive raw touch events
* from the window manager as the user interacts with the window
* that is currently displaying the wallpaper. By default they
@@ -930,7 +965,7 @@
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
- Log.w(TAG, "Ignoring updateSurface: destroyed");
+ Log.w(TAG, "Ignoring updateSurface due to destroyed");
}
boolean fixedSize = false;
@@ -1197,7 +1232,6 @@
+ this);
onVisibilityChanged(false);
}
-
} finally {
mIsCreating = false;
mSurfaceCreated = true;
@@ -1207,7 +1241,7 @@
processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
reposition();
- mIWallpaperEngine.reportShown();
+ reportEngineShown(shouldWaitForEngineShown());
}
} catch (RemoteException ex) {
}
@@ -2048,6 +2082,8 @@
mShownReported = true;
try {
mConnection.engineShown(this);
+ Log.d(TAG, "Wallpaper has updated the surface:"
+ + mWallpaperManager.getWallpaperInfo());
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
return;
@@ -2201,6 +2237,9 @@
// Connection went away, nothing to do in here.
}
} break;
+ case MSG_REPORT_SHOWN: {
+ reportShown();
+ } break;
default :
Log.w(TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 362ea8c..5e647a4 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -115,7 +115,7 @@
@NonNull AttributionSource attributionSource) {
try {
if (mCurrentCallback == null) {
- boolean preflightPermissionCheckPassed = checkPermissionForPreflight(
+ boolean preflightPermissionCheckPassed = checkPermissionForPreflightNotHardDenied(
attributionSource);
if (preflightPermissionCheckPassed) {
if (DBG) {
@@ -470,10 +470,11 @@
return mStartedDataDelivery;
}
- private boolean checkPermissionForPreflight(AttributionSource attributionSource) {
- return PermissionChecker.checkPermissionForPreflight(RecognitionService.this,
- Manifest.permission.RECORD_AUDIO, attributionSource)
- == PermissionChecker.PERMISSION_GRANTED;
+ private boolean checkPermissionForPreflightNotHardDenied(AttributionSource attributionSource) {
+ int result = PermissionChecker.checkPermissionForPreflight(RecognitionService.this,
+ Manifest.permission.RECORD_AUDIO, attributionSource);
+ return result == PermissionChecker.PERMISSION_GRANTED
+ || result == PermissionChecker.PERMISSION_SOFT_DENIED;
}
void finishDataDelivery() {
diff --git a/core/java/android/text/method/TranslationTransformationMethod.java b/core/java/android/text/method/TranslationTransformationMethod.java
index 80387aa..43d186e 100644
--- a/core/java/android/text/method/TranslationTransformationMethod.java
+++ b/core/java/android/text/method/TranslationTransformationMethod.java
@@ -62,6 +62,13 @@
return mOriginalTranslationMethod;
}
+ /**
+ * Returns the {@link TextView}'s {@link ViewTranslationResponse}.
+ */
+ public ViewTranslationResponse getViewTranslationResponse() {
+ return mTranslationResponse;
+ }
+
@Override
public CharSequence getTransformation(CharSequence source, View view) {
if (!mAllowLengthChanges) {
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8021636..a7ecf1f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -814,9 +814,10 @@
* @param displayId The display associated with the window context
* @param options A bundle used to pass window-related options and choose the right DisplayArea
*
- * @return {@code true} if the WindowContext is attached to the DisplayArea successfully.
+ * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is
+ * attached to the DisplayArea successfully. {@code null}, otherwise.
*/
- boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
+ Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
in Bundle options);
/**
diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java
index 8808827..758f9ab 100644
--- a/core/java/android/view/ScrollCaptureResponse.java
+++ b/core/java/android/view/ScrollCaptureResponse.java
@@ -53,6 +53,10 @@
@Nullable
private String mWindowTitle = null;
+ /** The package which owns the window. */
+ @Nullable
+ private String mPackageName = null;
+
/** Carries additional logging and debugging information when enabled. */
@NonNull
@DataClass.PluralOf("message")
@@ -77,7 +81,7 @@
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -97,6 +101,7 @@
@Nullable Rect windowBounds,
@Nullable Rect boundsInWindow,
@Nullable String windowTitle,
+ @Nullable String packageName,
@NonNull ArrayList<String> messages) {
this.mDescription = description;
com.android.internal.util.AnnotationValidations.validate(
@@ -105,6 +110,7 @@
this.mWindowBounds = windowBounds;
this.mBoundsInWindow = boundsInWindow;
this.mWindowTitle = windowTitle;
+ this.mPackageName = packageName;
this.mMessages = messages;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mMessages);
@@ -153,6 +159,14 @@
}
/**
+ * The package name of the process the window is owned by.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* Carries additional logging and debugging information when enabled.
*/
@DataClass.Generated.Member
@@ -172,6 +186,7 @@
"windowBounds = " + mWindowBounds + ", " +
"boundsInWindow = " + mBoundsInWindow + ", " +
"windowTitle = " + mWindowTitle + ", " +
+ "packageName = " + mPackageName + ", " +
"messages = " + mMessages +
" }";
}
@@ -187,12 +202,14 @@
if (mWindowBounds != null) flg |= 0x4;
if (mBoundsInWindow != null) flg |= 0x8;
if (mWindowTitle != null) flg |= 0x10;
+ if (mPackageName != null) flg |= 0x20;
dest.writeByte(flg);
dest.writeString(mDescription);
if (mConnection != null) dest.writeStrongInterface(mConnection);
if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags);
if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags);
if (mWindowTitle != null) dest.writeString(mWindowTitle);
+ if (mPackageName != null) dest.writeString(mPackageName);
dest.writeStringList(mMessages);
}
@@ -213,6 +230,7 @@
Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
String windowTitle = (flg & 0x10) == 0 ? null : in.readString();
+ String packageName = (flg & 0x20) == 0 ? null : in.readString();
ArrayList<String> messages = new ArrayList<>();
in.readStringList(messages);
@@ -223,6 +241,7 @@
this.mWindowBounds = windowBounds;
this.mBoundsInWindow = boundsInWindow;
this.mWindowTitle = windowTitle;
+ this.mPackageName = packageName;
this.mMessages = messages;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mMessages);
@@ -256,6 +275,7 @@
private @Nullable Rect mWindowBounds;
private @Nullable Rect mBoundsInWindow;
private @Nullable String mWindowTitle;
+ private @Nullable String mPackageName;
private @NonNull ArrayList<String> mMessages;
private long mBuilderFieldsSet = 0L;
@@ -319,12 +339,23 @@
}
/**
+ * The package name of the process the window is owned by.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mPackageName = value;
+ return this;
+ }
+
+ /**
* Carries additional logging and debugging information when enabled.
*/
@DataClass.Generated.Member
public @NonNull Builder setMessages(@NonNull ArrayList<String> value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20;
+ mBuilderFieldsSet |= 0x40;
mMessages = value;
return this;
}
@@ -340,7 +371,7 @@
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ScrollCaptureResponse build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x40; // Mark builder used
+ mBuilderFieldsSet |= 0x80; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mDescription = "";
@@ -358,6 +389,9 @@
mWindowTitle = null;
}
if ((mBuilderFieldsSet & 0x20) == 0) {
+ mPackageName = null;
+ }
+ if ((mBuilderFieldsSet & 0x40) == 0) {
mMessages = new ArrayList<>();
}
ScrollCaptureResponse o = new ScrollCaptureResponse(
@@ -366,12 +400,13 @@
mWindowBounds,
mBoundsInWindow,
mWindowTitle,
+ mPackageName,
mMessages);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x40) != 0) {
+ if ((mBuilderFieldsSet & 0x80) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -379,10 +414,10 @@
}
@DataClass.Generated(
- time = 1614833185795L,
- codegenVersion = "1.0.22",
+ time = 1628630366187L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f4223fb..cf12955 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -20830,13 +20830,14 @@
mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
}
+ notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
+
mAttachInfo = null;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchDetachedFromWindow();
}
notifyEnterOrExitForAutoFillIfNeeded(false);
- notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 93138e5..0ecd76f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9497,6 +9497,7 @@
ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder();
response.setWindowTitle(getTitle().toString());
+ response.setPackageName(mContext.getPackageName());
StringWriter writer = new StringWriter();
IndentingPrintWriter pw = new IndentingPrintWriter(writer);
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
index ec55a02..3365c70 100644
--- a/core/java/android/view/animation/TranslateAnimation.java
+++ b/core/java/android/view/animation/TranslateAnimation.java
@@ -57,6 +57,9 @@
/** @hide */
protected float mToYDelta;
+ private int mWidth;
+ private int mParentWidth;
+
/**
* Constructor used when a TranslateAnimation is loaded from a resource.
*
@@ -179,5 +182,60 @@
mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
+
+ mWidth = width;
+ mParentWidth = parentWidth;
+ }
+
+ /**
+ * Checks whether or not the translation is exclusively an x axis translation.
+ *
+ * @hide
+ */
+ public boolean isXAxisTransition() {
+ return mFromXDelta - mToXDelta != 0 && mFromYDelta - mToYDelta == 0;
+ }
+
+ /**
+ * Checks whether or not the translation is a full width x axis slide in or out translation.
+ *
+ * @hide
+ */
+ public boolean isFullWidthTranslate() {
+ boolean isXAxisSlideTransition =
+ isSlideInLeft() || isSlideOutRight() || isSlideInRight() || isSlideOutLeft();
+ return mWidth == mParentWidth && isXAxisSlideTransition;
+ }
+
+ private boolean isSlideInLeft() {
+ boolean startsOutOfParentOnLeft = mFromXDelta <= -mWidth;
+ return startsOutOfParentOnLeft && endsXEnclosedWithinParent();
+ }
+
+ private boolean isSlideOutRight() {
+ boolean endOutOfParentOnRight = mToXDelta >= mParentWidth;
+ return startsXEnclosedWithinParent() && endOutOfParentOnRight;
+ }
+
+ private boolean isSlideInRight() {
+ boolean startsOutOfParentOnRight = mFromXDelta >= mParentWidth;
+ return startsOutOfParentOnRight && endsXEnclosedWithinParent();
+ }
+
+ private boolean isSlideOutLeft() {
+ boolean endOutOfParentOnLeft = mToXDelta <= -mWidth;
+ return startsXEnclosedWithinParent() && endOutOfParentOnLeft;
+ }
+
+ private boolean endsXEnclosedWithinParent() {
+ return mWidth <= mParentWidth
+ && mToXDelta + mWidth <= mParentWidth
+ && mToXDelta >= 0;
+ }
+
+ private boolean startsXEnclosedWithinParent() {
+ return mWidth <= mParentWidth
+ && mFromXDelta + mWidth <= mParentWidth
+ && mFromXDelta >= 0;
}
}
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 442d099..d078c2c 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -431,15 +431,19 @@
continue;
}
mActivity.runOnUiThread(() -> {
+ ViewTranslationCallback callback = view.getViewTranslationCallback();
if (view.getViewTranslationResponse() != null
&& view.getViewTranslationResponse().equals(response)) {
- if (DEBUG) {
- Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
- + ". Ignoring.");
+ if (callback instanceof TextViewTranslationCallback) {
+ if (((TextViewTranslationCallback) callback).isShowingTranslation()) {
+ if (DEBUG) {
+ Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
+ + ". Ignoring.");
+ }
+ return;
+ }
}
- return;
}
- ViewTranslationCallback callback = view.getViewTranslationCallback();
if (callback == null) {
if (view instanceof TextView) {
// developer doesn't provide their override, we set the default TextView
@@ -598,9 +602,8 @@
final View rootView = roots.get(rootNum).getView();
if (rootView instanceof ViewGroup) {
findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds);
- } else {
- addViewIfNeeded(sourceViewIds, rootView);
}
+ addViewIfNeeded(sourceViewIds, rootView);
}
}
@@ -611,9 +614,8 @@
final View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup) {
findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds);
- } else {
- addViewIfNeeded(sourceViewIds, child);
}
+ addViewIfNeeded(sourceViewIds, child);
}
}
diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java
index a075662..66c028b 100644
--- a/core/java/android/view/translation/ViewTranslationCallback.java
+++ b/core/java/android/view/translation/ViewTranslationCallback.java
@@ -41,6 +41,11 @@
* method will not be called before {@link View#onViewTranslationResponse} or
* {@link View#onVirtualViewTranslationResponses}.
*
+ * <p> NOTE: It is possible the user changes text that causes a new
+ * {@link ViewTranslationResponse} returns to show the new translation. If you cache the
+ * {@link ViewTranslationResponse} here, you should remember to keep the cached value up
+ * to date.
+ *
* <p> NOTE: For TextView implementation, {@link ContentCaptureSession#notifyViewTextChanged}
* shouldn't be called with the translated text, simply calling setText() here will trigger the
* method. You should either override {@code View#onProvideContentCaptureStructure()} to report
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 91fc5a5..fe5eb08 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -740,6 +740,16 @@
}
@Override
+ public UserHandle getUser() {
+ return mContextForResources.getUser();
+ }
+
+ @Override
+ public int getUserId() {
+ return mContextForResources.getUserId();
+ }
+
+ @Override
public boolean isRestricted() {
// Override isRestricted and direct to resource's implementation. The isRestricted is
// used for determining the risky resources loading, e.g. fonts, thus direct to context
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 9d60009..4a78f3e 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -64,13 +64,24 @@
*/
@Override
public boolean onShowTranslation(@NonNull View view) {
+ if (mIsShowingTranslation) {
+ if (DEBUG) {
+ Log.d(TAG, view + " is already showing translated text.");
+ }
+ return false;
+ }
ViewTranslationResponse response = view.getViewTranslationResponse();
if (response == null) {
Log.e(TAG, "onShowTranslation() shouldn't be called before "
+ "onViewTranslationResponse().");
return false;
}
- if (mTranslationTransformation == null) {
+ // It is possible user changes text and new translation response returns, system should
+ // update the translation response to keep the result up to date.
+ // Because TextView.setTransformationMethod() will skip the same TransformationMethod
+ // instance, we should create a new one to let new translation can work.
+ if (mTranslationTransformation == null
+ || !response.equals(mTranslationTransformation.getViewTranslationResponse())) {
TransformationMethod originalTranslationMethod =
((TextView) view).getTransformationMethod();
mTranslationTransformation = new TranslationTransformationMethod(response,
@@ -147,7 +158,7 @@
return true;
}
- boolean isShowingTranslation() {
+ public boolean isShowingTranslation() {
return mIsShowingTranslation;
}
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 901625b..6d0a6bd 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -26,7 +26,6 @@
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.os.IBinder;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -67,7 +66,7 @@
mType = type;
mOptions = options;
mWindowManager = createWindowContextWindowManager(this);
- IBinder token = getWindowContextToken();
+ WindowTokenClient token = (WindowTokenClient) getWindowContextToken();
mController = new WindowContextController(token);
Reference.reachabilityFence(this);
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index d84f571..505b450 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -46,7 +47,7 @@
@VisibleForTesting
public boolean mAttachedToDisplayArea;
@NonNull
- private final IBinder mToken;
+ private final WindowTokenClient mToken;
/**
* Window Context Controller constructor
@@ -54,14 +55,13 @@
* @param token The token used to attach to a window manager node. It is usually from
* {@link Context#getWindowContextToken()}.
*/
- public WindowContextController(@NonNull IBinder token) {
- mToken = token;
- mWms = WindowManagerGlobal.getWindowManagerService();
+ public WindowContextController(@NonNull WindowTokenClient token) {
+ this(token, WindowManagerGlobal.getWindowManagerService());
}
/** Used for test only. DO NOT USE it in production code. */
@VisibleForTesting
- public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+ public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) {
mToken = token;
mWms = mockWms;
}
@@ -81,8 +81,14 @@
+ "a DisplayArea once.");
}
try {
- mAttachedToDisplayArea = mWms.attachWindowContextToDisplayArea(mToken, type, displayId,
- options);
+ final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type,
+ displayId, options);
+ if (configuration != null) {
+ mAttachedToDisplayArea = true;
+ // Send the DisplayArea's configuration to WindowContext directly instead of
+ // waiting for dispatching from WMS.
+ mToken.onConfigurationChanged(configuration, displayId);
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 6abf557..4dcd2e7 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -24,6 +24,8 @@
import android.os.Bundle;
import android.os.IBinder;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.ref.WeakReference;
/**
@@ -33,7 +35,7 @@
* {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
*
* @see WindowContext
- * @see android.view.IWindowManager#registerWindowContextListener(IBinder, int, int, Bundle)
+ * @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
*
* @hide
*/
@@ -50,8 +52,8 @@
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
* can only attach one {@link Context}.
* <p>This method must be called before invoking
- * {@link android.view.IWindowManager#registerWindowContextListener(IBinder, int, int,
- * Bundle, boolean)}.<p/>
+ * {@link android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int,
+ * Bundle)}.<p/>
*
* @param context context to be attached
* @throws IllegalStateException if attached context has already existed.
@@ -63,6 +65,13 @@
mContextRef = new WeakReference<>(context);
}
+ /**
+ * Called when {@link Configuration} updates from the server side receive.
+ *
+ * @param newConfig the updated {@link Configuration}
+ * @param newDisplayId the updated {@link android.view.Display} ID
+ */
+ @VisibleForTesting
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
final Context context = mContextRef.get();
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 84354d9..ec224e5 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -72,17 +72,19 @@
private Resources mSuspendingAppResources;
private SuspendDialogInfo mSuppliedDialogInfo;
private Bundle mOptions;
- private BroadcastReceiver mUnsuspendReceiver = new BroadcastReceiver() {
+ private BroadcastReceiver mSuspendModifiedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(intent.getAction())) {
- final String[] unsuspended = intent.getStringArrayExtra(
+ if (Intent.ACTION_PACKAGES_SUSPENSION_CHANGED.equals(intent.getAction())) {
+ // Suspension conditions were modified, dismiss any related visible dialogs.
+ final String[] modified = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_PACKAGE_LIST);
- if (ArrayUtils.contains(unsuspended, mSuspendedPackage)) {
+ if (ArrayUtils.contains(modified, mSuspendedPackage)) {
if (!isFinishing()) {
- Slog.w(TAG, "Package " + mSuspendedPackage
- + " got unsuspended while the dialog was visible. Finishing.");
+ Slog.w(TAG, "Package " + mSuspendedPackage + " has modified"
+ + " suspension conditions while dialog was visible. Finishing.");
SuspendedAppActivity.this.finish();
+ // TODO (b/198201994): reload the suspend dialog to show most relevant info
}
}
}
@@ -245,15 +247,16 @@
setupAlert();
- final IntentFilter unsuspendFilter = new IntentFilter(Intent.ACTION_PACKAGES_UNSUSPENDED);
- registerReceiverAsUser(mUnsuspendReceiver, UserHandle.of(mUserId), unsuspendFilter, null,
- null);
+ final IntentFilter suspendModifiedFilter =
+ new IntentFilter(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED);
+ registerReceiverAsUser(mSuspendModifiedReceiver, UserHandle.of(mUserId),
+ suspendModifiedFilter, null, null);
}
@Override
protected void onDestroy() {
super.onDestroy();
- unregisterReceiver(mUnsuspendReceiver);
+ unregisterReceiver(mSuspendModifiedReceiver);
}
private void requestDismissKeyguardIfNeeded(CharSequence dismissMessage) {
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index ce75f45..068b882 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -165,7 +165,7 @@
// Now fetch app icon and raster with no badging even in work profile
Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info)
- .getIconBitmap(android.os.Process.myUserHandle());
+ .getIconBitmap(mContext.getUser());
// Raster target drawable with appIcon as a badge
SimpleIconFactory sif = SimpleIconFactory.obtain(mContext);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8c63f38..a817119 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -707,6 +707,10 @@
* Mapping isolated uids to the actual owning app uid.
*/
final SparseIntArray mIsolatedUids = new SparseIntArray();
+ /**
+ * Internal reference count of isolated uids.
+ */
+ final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
/**
* The statistics we have collected organized by uids.
@@ -3897,6 +3901,7 @@
public void addIsolatedUidLocked(int isolatedUid, int appUid,
long elapsedRealtimeMs, long uptimeMs) {
mIsolatedUids.put(isolatedUid, appUid);
+ mIsolatedUidRefCounts.put(isolatedUid, 1);
final Uid u = getUidStatsLocked(appUid, elapsedRealtimeMs, uptimeMs);
u.addIsolatedUid(isolatedUid);
}
@@ -3915,19 +3920,51 @@
}
/**
- * This should only be called after the cpu times have been read.
+ * Isolated uid should only be removed after all wakelocks associated with the uid are stopped
+ * and the cpu time-in-state has been read one last time for the uid.
+ *
* @see #scheduleRemoveIsolatedUidLocked(int, int)
+ *
+ * @return true if the isolated uid is actually removed.
*/
@GuardedBy("this")
- public void removeIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, long uptimeMs) {
+ public boolean maybeRemoveIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs,
+ long uptimeMs) {
+ final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1;
+ if (refCount > 0) {
+ // Isolated uid is still being tracked
+ mIsolatedUidRefCounts.put(isolatedUid, refCount);
+ return false;
+ }
+
final int idx = mIsolatedUids.indexOfKey(isolatedUid);
if (idx >= 0) {
final int ownerUid = mIsolatedUids.valueAt(idx);
final Uid u = getUidStatsLocked(ownerUid, elapsedRealtimeMs, uptimeMs);
u.removeIsolatedUid(isolatedUid);
mIsolatedUids.removeAt(idx);
+ mIsolatedUidRefCounts.delete(isolatedUid);
+ } else {
+ Slog.w(TAG, "Attempted to remove untracked isolated uid (" + isolatedUid + ")");
}
mPendingRemovedUids.add(new UidToRemove(isolatedUid, elapsedRealtimeMs));
+
+ return true;
+ }
+
+ /**
+ * Increment the ref count for an isolated uid.
+ * call #maybeRemoveIsolatedUidLocked to decrement.
+ */
+ public void incrementIsolatedUidRefCount(int uid) {
+ final int refCount = mIsolatedUidRefCounts.get(uid, 0);
+ if (refCount <= 0) {
+ // Uid is not mapped or referenced
+ Slog.w(TAG,
+ "Attempted to increment ref counted of untracked isolated uid (" + uid + ")");
+ return;
+ }
+ mIsolatedUidRefCounts.put(uid, refCount + 1);
}
public int mapUid(int uid) {
@@ -4287,7 +4324,7 @@
public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
int type, boolean unimportantForLogging, long elapsedRealtimeMs, long uptimeMs) {
- uid = mapUid(uid);
+ final int mappedUid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
// Only care about partial wake locks, since full wake locks
// will be canceled when the user puts the screen to sleep.
@@ -4297,9 +4334,9 @@
}
if (mRecordAllHistory) {
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
- uid, 0)) {
+ mappedUid, 0)) {
addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
- HistoryItem.EVENT_WAKE_LOCK_START, historyName, uid);
+ HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
@@ -4308,7 +4345,7 @@
+ Integer.toHexString(mHistoryCur.states));
mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
- mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+ mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = mappedUid;
mWakeLockImportant = !unimportantForLogging;
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
} else if (!mWakeLockImportant && !unimportantForLogging
@@ -4318,14 +4355,19 @@
mHistoryLastWritten.wakelockTag = null;
mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
- mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+ mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = mappedUid;
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
mWakeLockImportant = true;
}
mWakeLockNesting++;
}
- if (uid >= 0) {
+ if (mappedUid >= 0) {
+ if (mappedUid != uid) {
+ // Prevent the isolated uid mapping from being removed while the wakelock is
+ // being held.
+ incrementIsolatedUidRefCount(uid);
+ }
if (mOnBatteryScreenOffTimeBase.isRunning()) {
// We only update the cpu time when a wake lock is acquired if the screen is off.
// If the screen is on, we don't distribute the power amongst partial wakelocks.
@@ -4335,7 +4377,7 @@
requestWakelockCpuUpdate();
}
- getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+ getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
if (wc != null) {
@@ -4343,8 +4385,8 @@
wc.getTags(), getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
} else {
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, uid,
- null, getPowerManagerWakeLockLevel(type), name,
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+ mappedUid, null, getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
}
}
@@ -4358,7 +4400,7 @@
public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
int type, long elapsedRealtimeMs, long uptimeMs) {
- uid = mapUid(uid);
+ final int mappedUid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
mWakeLockNesting--;
if (mRecordAllHistory) {
@@ -4366,9 +4408,9 @@
historyName = name;
}
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
- uid, 0)) {
+ mappedUid, 0)) {
addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
- HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, uid);
+ HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
@@ -4380,7 +4422,7 @@
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
}
- if (uid >= 0) {
+ if (mappedUid >= 0) {
if (mOnBatteryScreenOffTimeBase.isRunning()) {
if (DEBUG_ENERGY_CPU) {
Slog.d(TAG, "Updating cpu time because of -wake_lock");
@@ -4388,17 +4430,22 @@
requestWakelockCpuUpdate();
}
- getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+ getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
if (wc != null) {
FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
wc.getTags(), getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
} else {
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, uid,
- null, getPowerManagerWakeLockLevel(type), name,
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+ mappedUid, null, getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
}
+
+ if (mappedUid != uid) {
+ // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
+ maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
+ }
}
}
@@ -8571,7 +8618,7 @@
* inactive so can be dropped.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public boolean reset(long uptimeUs, long realtimeUs) {
+ public boolean reset(long uptimeUs, long realtimeUs, int resetReason) {
boolean active = false;
mOnBatteryBackgroundTimeBase.init(uptimeUs, realtimeUs);
@@ -8641,7 +8688,11 @@
resetIfNotNull(mBluetoothControllerActivity, false, realtimeUs);
resetIfNotNull(mModemControllerActivity, false, realtimeUs);
- MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+ if (resetReason == RESET_REASON_MEASURED_ENERGY_BUCKETS_CHANGE) {
+ mUidMeasuredEnergyStats = null;
+ } else {
+ MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+ }
resetIfNotNull(mUserCpuTime, false, realtimeUs);
resetIfNotNull(mSystemCpuTime, false, realtimeUs);
@@ -11324,7 +11375,7 @@
mNumConnectivityChange = 0;
for (int i=0; i<mUidStats.size(); i++) {
- if (mUidStats.valueAt(i).reset(uptimeUs, elapsedRealtimeUs)) {
+ if (mUidStats.valueAt(i).reset(uptimeUs, elapsedRealtimeUs, resetReason)) {
mUidStats.valueAt(i).detachFromTimeBase();
mUidStats.remove(mUidStats.keyAt(i));
i--;
@@ -16761,6 +16812,15 @@
pw.print("UIDs removed since the later of device start or stats reset: ");
pw.println(mNumUidsRemoved);
+ pw.println("Currently mapped isolated uids:");
+ final int numIsolatedUids = mIsolatedUids.size();
+ for (int i = 0; i < numIsolatedUids; i++) {
+ final int isolatedUid = mIsolatedUids.keyAt(i);
+ final int ownerUid = mIsolatedUids.valueAt(i);
+ final int refCount = mIsolatedUidRefCounts.get(isolatedUid);
+ pw.println(" " + isolatedUid + "->" + ownerUid + " (ref count = " + refCount + ")");
+ }
+
pw.println();
dumpConstantsLocked(pw);
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 0038579..980aec1 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -29,6 +29,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -234,8 +235,9 @@
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
+ final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames();
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- mStats.getCustomEnergyConsumerNames(), includePowerModels);
+ customEnergyConsumerNames, includePowerModels);
if (mBatteryUsageStatsStore == null) {
Log.e(TAG, "BatteryUsageStatsStore is unavailable");
return builder.build();
@@ -247,7 +249,14 @@
final BatteryUsageStats snapshot =
mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
if (snapshot != null) {
- builder.add(snapshot);
+ if (Arrays.equals(snapshot.getCustomPowerComponentNames(),
+ customEnergyConsumerNames)) {
+ builder.add(snapshot);
+ } else {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
+ + "custom power components: "
+ + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+ }
}
}
}
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index 9b51a8e..bb307a0 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -21,13 +21,18 @@
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
+import android.util.Slog;
import android.util.SparseArray;
+import java.util.Arrays;
+
/**
* Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
* {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
*/
public class CustomMeasuredPowerCalculator extends PowerCalculator {
+ private static final String TAG = "CustomMeasuredPowerCalc";
+
public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
}
@@ -76,9 +81,9 @@
if (totalPowerMah == null) {
newTotalPowerMah = new double[customMeasuredPowerMah.length];
} else if (totalPowerMah.length != customMeasuredPowerMah.length) {
- newTotalPowerMah = new double[customMeasuredPowerMah.length];
- System.arraycopy(totalPowerMah, 0, newTotalPowerMah, 0,
- customMeasuredPowerMah.length);
+ Slog.wtf(TAG, "Number of custom energy components is not the same for all apps: "
+ + totalPowerMah.length + ", " + customMeasuredPowerMah.length);
+ newTotalPowerMah = Arrays.copyOf(totalPowerMah, customMeasuredPowerMah.length);
} else {
newTotalPowerMah = totalPowerMah;
}
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 776a705..3915b0e 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -215,6 +215,9 @@
+ "ms tx=" + txTime + "ms power=" + formatCharge(
powerDurationAndTraffic.powerMah));
}
+ } else {
+ powerDurationAndTraffic.durationMs = 0;
+ powerDurationAndTraffic.powerMah = 0;
}
} else {
final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000;
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index b321ac0..a09c823 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -124,7 +124,6 @@
Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
- sendCloseSystemWindows();
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
startCallActivity();
@@ -147,7 +146,6 @@
dispatcher.performedLongPress(event);
if (isUserSetupComplete()) {
mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- sendCloseSystemWindows();
// Broadcast an intent that the Camera button was longpressed
Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -178,7 +176,6 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- sendCloseSystemWindows();
getSearchManager().stopSearch();
mContext.startActivity(intent);
// Only clear this if we successfully start the
@@ -272,7 +269,6 @@
@UnsupportedAppUsage
void startCallActivity() {
- sendCloseSystemWindows();
Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
@@ -319,10 +315,6 @@
return mMediaSessionManager;
}
- void sendCloseSystemWindows() {
- PhoneWindow.sendCloseSystemWindows(mContext, null);
- }
-
private void handleVolumeKeyEvent(KeyEvent keyEvent) {
getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent,
AudioManager.USE_DEFAULT_STREAM_TYPE);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 10f14b4..ed6415d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -148,7 +148,7 @@
*/
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId,
- String opPackageName, long operationId, int multiSensorConfig);
+ long operationId, String opPackageName, long requestId, int multiSensorConfig);
/**
* Used to notify the authentication dialog that a biometric has been authenticated.
*/
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index e7d6d6c..b3499db 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -110,7 +110,8 @@
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId, int multiSensorConfig);
+ int userId, long operationId, String opPackageName, long requestId,
+ int multiSensorConfig);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 8b3c133..7a712e5 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -291,10 +291,10 @@
* Finds a suitable color such that there's enough contrast.
*
* @param color the color to start searching from.
- * @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
- * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+ * @param other the color to ensure contrast against. Assumed to be lighter than {@code color}
+ * @param findFg if true, we assume {@code color} is a foreground, otherwise a background.
* @param minRatio the minimum contrast ratio required.
- * @return a color with the same hue as {@param color}, potentially darkened to meet the
+ * @return a color with the same hue as {@code color}, potentially darkened to meet the
* contrast ratio.
*/
public static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
@@ -331,7 +331,7 @@
* @param color the color to start searching from.
* @param backgroundColor the color to ensure contrast against.
* @param minRatio the minimum contrast ratio required.
- * @return the same color as {@param color} with potentially modified alpha to meet contrast
+ * @return the same color as {@code color} with potentially modified alpha to meet contrast
*/
public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) {
int fg = color;
@@ -361,10 +361,10 @@
* Finds a suitable color such that there's enough contrast.
*
* @param color the color to start searching from.
- * @param other the color to ensure contrast against. Assumed to be darker than {@param color}
- * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+ * @param other the color to ensure contrast against. Assumed to be darker than {@code color}
+ * @param findFg if true, we assume {@code color} is a foreground, otherwise a background.
* @param minRatio the minimum contrast ratio required.
- * @return a color with the same hue as {@param color}, potentially darkened to meet the
+ * @return a color with the same hue as {@code color}, potentially lightened to meet the
* contrast ratio.
*/
public static int findContrastColorAgainstDark(int color, int other, boolean findFg,
@@ -393,7 +393,8 @@
low = l;
}
}
- return findFg ? fg : bg;
+ hsl[2] = high;
+ return ColorUtilsFromCompat.HSLToColor(hsl);
}
public static int ensureTextContrastOnBlack(int color) {
@@ -452,7 +453,7 @@
}
/**
- * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
+ * Resolves {@code color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
*/
public static int resolveColor(Context context, int color, boolean defaultBackgroundIsDark) {
if (color == Notification.COLOR_DEFAULT) {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 498505c..cd1bbb6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,6 +17,7 @@
package com.android.internal.widget;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -1272,6 +1273,14 @@
}
/**
+ * Whether the user is not allowed to set any credentials via PASSWORD_QUALITY_MANAGED.
+ */
+ public boolean isCredentialsDisabledForUser(int userId) {
+ return getDevicePolicyManager().getPasswordQuality(/* admin= */ null, userId)
+ == PASSWORD_QUALITY_MANAGED;
+ }
+
+ /**
* @see StrongAuthTracker#isTrustAllowedForUser
*/
public boolean isTrustAllowedForUser(int userId) {
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 0c2d2a9..3191e23 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -53,6 +53,8 @@
private int mEmphasizedHeight;
private int mRegularHeight;
@DimenRes private int mCollapsibleIndentDimen = R.dimen.notification_actions_padding_start;
+ int mNumNotGoneChildren;
+ int mNumPriorityChildren;
public NotificationActionListLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -76,15 +78,14 @@
&& ((EmphasizedNotificationButton) actionView).isPriority();
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int N = getChildCount();
+ private void countAndRebuildMeasureOrder() {
+ final int numChildren = getChildCount();
int textViews = 0;
int otherViews = 0;
- int notGoneChildren = 0;
- int priorityChildren = 0;
+ mNumNotGoneChildren = 0;
+ mNumPriorityChildren = 0;
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numChildren; i++) {
View c = getChildAt(i);
if (c instanceof TextView) {
textViews++;
@@ -92,9 +93,9 @@
otherViews++;
}
if (c.getVisibility() != GONE) {
- notGoneChildren++;
+ mNumNotGoneChildren++;
if (isPriority(c)) {
- priorityChildren++;
+ mNumPriorityChildren++;
}
}
}
@@ -119,17 +120,20 @@
if (needRebuild) {
rebuildMeasureOrder(textViews, otherViews);
}
+ }
+ private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
+ boolean collapsePriorityActions) {
+ final int numChildren = getChildCount();
final boolean constrained =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED;
-
- final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
final int otherSize = mMeasureOrderOther.size();
int usedWidth = 0;
+ int maxPriorityWidth = 0;
int measuredChildren = 0;
int measuredPriorityChildren = 0;
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numChildren; i++) {
// Measure shortest children first. To avoid measuring twice, we approximate by looking
// at the text length.
final boolean isPriority;
@@ -154,12 +158,20 @@
// measure in the order of (approx.) size, a large view can still take more than its
// share if the others are small.
int availableWidth = innerWidth - usedWidth;
- int unmeasuredChildren = notGoneChildren - measuredChildren;
+ int unmeasuredChildren = mNumNotGoneChildren - measuredChildren;
int maxWidthForChild = availableWidth / unmeasuredChildren;
- if (isPriority) {
+ if (isPriority && collapsePriorityActions) {
+ // Collapsing the actions to just the width required to show the icon.
+ if (maxPriorityWidth == 0) {
+ maxPriorityWidth = getResources().getDimensionPixelSize(
+ R.dimen.notification_actions_collapsed_priority_width);
+ }
+ maxWidthForChild = maxPriorityWidth + lp.leftMargin + lp.rightMargin;
+ } else if (isPriority) {
// Priority children get a larger maximum share of the total space:
// maximum priority share = (nPriority + 1) / (MAX + 1)
- int unmeasuredPriorityChildren = priorityChildren - measuredPriorityChildren;
+ int unmeasuredPriorityChildren = mNumPriorityChildren
+ - measuredPriorityChildren;
int unmeasuredOtherChildren = unmeasuredChildren - unmeasuredPriorityChildren;
int widthReservedForOtherChildren = innerWidth * unmeasuredOtherChildren
/ (Notification.MAX_ACTION_BUTTONS + 1);
@@ -187,6 +199,19 @@
} else {
mExtraStartPadding = 0;
}
+ return usedWidth;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ countAndRebuildMeasureOrder();
+ final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
+ int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ false /* collapsePriorityButtons */);
+ if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ true /* collapsePriorityButtons */);
+ }
mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index b40491a..f44e829 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,8 +50,7 @@
}
void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
- jstring devOptIn, jobjectArray featuresObj, jobject rulesFd,
- jlong rulesOffset, jlong rulesLength) {
+ jstring devOptIn, jobjectArray featuresObj) {
ScopedUtfChars pathChars(env, path);
ScopedUtfChars appNameChars(env, appName);
ScopedUtfChars devOptInChars(env, devOptIn);
@@ -74,11 +73,8 @@
}
}
- int rulesFd_native = jniGetFDFromFileDescriptor(env, rulesFd);
-
android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
- devOptInChars.c_str(), features,
- rulesFd_native, rulesOffset, rulesLength);
+ devOptInChars.c_str(), features);
}
bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
@@ -124,8 +120,7 @@
{"setInjectLayersPrSetDumpable", "()Z",
reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
{"setAngleInfo",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/io/"
- "FileDescriptor;JJ)V",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
reinterpret_cast<void*>(setAngleInfo_native)},
{"getShouldUseAngle", "(Ljava/lang/String;)Z",
reinterpret_cast<void*>(shouldUseAngle_native)},
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index c3d1596..83e26f6 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -771,6 +771,8 @@
optional SettingProto power_manager_constants = 93;
reserved 94; // Used to be priv_app_oob_enabled
+ optional SettingProto power_button_long_press_duration_ms = 154 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
message PrepaidSetup {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -970,6 +972,7 @@
optional SettingProto usb_mass_storage_enabled = 127 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto use_google_mail = 128 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto use_open_wifi_package = 129 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto uwb_enabled = 155 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto vt_ims_enabled = 130 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto wait_for_debugger = 131 [ (android.privacy).dest = DEST_AUTOMATIC ];
@@ -1063,5 +1066,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 154;
+ // Next tag = 156;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bd89578..a774cb4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -55,6 +55,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENSION_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
<protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" />
<protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
@@ -252,6 +253,8 @@
android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
<protected-broadcast
android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
diff --git a/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
index 5add19b..aa38000 100644
--- a/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
@@ -18,19 +18,9 @@
-->
<!-- This should be kept in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:hasRoundedCorners="true"
android:shareInterpolator="false"
- android:zAdjustment="top">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -38,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the thumbnail around longer and fade out the thumbnail -->
<alpha android:fromAlpha="1.0" android:toAlpha="0"
diff --git a/core/res/res/anim-ldrtl/task_close_enter.xml b/core/res/res/anim-ldrtl/task_close_enter.xml
index e00141a..5ace46d 100644
--- a/core/res/res/anim-ldrtl/task_close_enter.xml
+++ b/core/res/res/anim-ldrtl/task_close_enter.xml
@@ -14,20 +14,9 @@
~ limitations under the License
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -35,34 +24,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim-ldrtl/task_close_exit.xml b/core/res/res/anim-ldrtl/task_close_exit.xml
index 71a44ae..0887019 100644
--- a/core/res/res/anim-ldrtl/task_close_exit.xml
+++ b/core/res/res/anim-ldrtl/task_close_exit.xml
@@ -15,19 +15,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -35,26 +24,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_enter.xml b/core/res/res/anim-ldrtl/task_open_enter.xml
index 7815f7d..52c74a6 100644
--- a/core/res/res/anim-ldrtl/task_open_enter.xml
+++ b/core/res/res/anim-ldrtl/task_open_enter.xml
@@ -16,20 +16,9 @@
<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
<!-- This should in sync with cross_profile_apps_thumbnail_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,34 +26,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
-</set>
\ No newline at end of file
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml b/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
index 5fccd6df..90ec071 100644
--- a/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
@@ -16,20 +16,9 @@
-->
<!-- This should in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,36 +26,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the transition around longer for the thumbnail, should be kept in sync with
cross_profile_apps_thumbmail.xml -->
@@ -75,4 +37,4 @@
android:toAlpha="1.0"
android:startOffset="717"
android:duration="200"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_exit.xml b/core/res/res/anim-ldrtl/task_open_exit.xml
index 025e1bd..88cdcce 100644
--- a/core/res/res/anim-ldrtl/task_open_exit.xml
+++ b/core/res/res/anim-ldrtl/task_open_exit.xml
@@ -15,19 +15,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -35,26 +24,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
index 2cfeecf..f6d7b72 100644
--- a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
@@ -19,18 +19,8 @@
<!-- This should be kept in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:zAdjustment="top">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -38,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the thumbnail around longer and fade out the thumbnail -->
<alpha android:fromAlpha="1.0" android:toAlpha="0"
diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml
index 487ff5c..52017b1 100644
--- a/core/res/res/anim/task_close_enter.xml
+++ b/core/res/res/anim/task_close_enter.xml
@@ -16,20 +16,9 @@
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,34 +26,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml
index afc3256c..3a8dd93 100644
--- a/core/res/res/anim/task_close_exit.xml
+++ b/core/res/res/anim/task_close_exit.xml
@@ -17,19 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -37,26 +26,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
index 0aafc1c..3c93438 100644
--- a/core/res/res/anim/task_open_enter.xml
+++ b/core/res/res/anim/task_open_enter.xml
@@ -15,23 +15,12 @@
** limitations under the License.
*/
-->
-<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
-<!-- This should in sync with cross_profile_apps_thumbnail_enter.xml -->
+<!-- This should be kept in sync with task_open_enter_cross_profile_apps.xml -->
+<!-- This should be kept in sync with cross_profile_apps_thumbnail_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -39,34 +28,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/task_open_enter_cross_profile_apps.xml b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
index 702f7ba..16249d1 100644
--- a/core/res/res/anim/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
@@ -18,20 +18,9 @@
-->
<!-- This should in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -39,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the transition around longer for the thumbnail, should be kept in sync with
cross_profile_apps_thumbmail.xml -->
diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml
index 691317d..21fec7f 100644
--- a/core/res/res/anim/task_open_exit.xml
+++ b/core/res/res/anim/task_open_exit.xml
@@ -17,19 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -37,26 +26,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/color/overview_background.xml b/core/res/res/color/overview_background.xml
new file mode 100644
index 0000000..45c6c25
--- /dev/null
+++ b/core/res/res/color/overview_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral2_500" android:lStar="87" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/overview_background_dark.xml b/core/res/res/color/overview_background_dark.xml
new file mode 100644
index 0000000..84f4fdf
--- /dev/null
+++ b/core/res/res/color/overview_background_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral2_500" android:lStar="35" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml
index 29c51f2a..7c09fb8 100644
--- a/core/res/res/drawable/btn_notification_emphasized.xml
+++ b/core/res/res/drawable/btn_notification_emphasized.xml
@@ -24,9 +24,9 @@
android:insetBottom="@dimen/button_inset_vertical_material">
<shape android:shape="rectangle">
<corners android:radius="@dimen/notification_action_button_radius" />
- <padding android:left="12dp"
+ <padding android:left="16dp"
android:top="@dimen/button_padding_vertical_material"
- android:right="12dp"
+ android:right="16dp"
android:bottom="@dimen/button_padding_vertical_material" />
<solid android:color="@color/white" />
</shape>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 2e4578c..783fabe 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -33,4 +33,6 @@
<color name="call_notification_answer_color">#5DBA80</color>
<color name="personal_apps_suspension_notification_color">#8AB4F8</color>
+
+ <color name="overview_background">@color/overview_background_dark</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ada4f0e..0e20e724 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -957,6 +957,20 @@
-->
<integer name="config_longPressOnPowerBehavior">5</integer>
+ <!-- The time in milliseconds after which a press on power button is considered "long". -->
+ <integer name="config_longPressOnPowerDurationMs">500</integer>
+
+ <!-- The possible UI options to be surfaced for configuring long press power on duration
+ action. Value set in config_longPressOnPowerDurationMs should be one of the available
+ options to allow users to restore default. -->
+ <integer-array name="config_longPressOnPowerDurationSettings">
+ <item>250</item>
+ <item>350</item>
+ <item>500</item>
+ <item>650</item>
+ <item>750</item>
+ </integer-array>
+
<!-- Whether the setting to change long press on power behaviour from default to assistant (5)
is available in Settings.
-->
@@ -4563,6 +4577,13 @@
<!-- Indicates whether device has a power button fingerprint sensor. -->
<bool name="config_is_powerbutton_fps" translatable="false" >false</bool>
+ <!-- When each intermediate UDFPS enroll stage ends, as a fraction of total progress. -->
+ <string-array name="config_udfps_enroll_stage_thresholds" translatable="false">
+ <item>0.25</item>
+ <item>0.5</item>
+ <item>0.75</item>
+ </string-array>
+
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index de7a117..a666a5b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -58,6 +58,9 @@
<!-- How much we expand the touchable region of the status bar below the notch to catch touches
that just start below the notch. -->
<dimen name="display_cutout_touchable_region_size">12dp</dimen>
+ <!-- The default margin used in immersive mode to capture the start of a swipe gesture from the
+ edge of the screen to show the system bars. -->
+ <dimen name="system_gestures_start_threshold">24dp</dimen>
<!-- Height of the bottom navigation bar frame; this is different than navigation_bar_height
where that is the height reported to all the other windows to resize themselves around the
@@ -237,6 +240,9 @@
value is calculated in ConversationLayout#updateActionListPadding() -->
<dimen name="notification_actions_padding_start">36dp</dimen>
+ <!-- The max width of a priority action button when it is collapsed to just the icon. -->
+ <dimen name="notification_actions_collapsed_priority_width">60dp</dimen>
+
<!-- The start padding to optionally use (e.g. if there's extra space) for CallStyle
notification actions.
this = conversation_content_start (80dp) - button inset (4dp) - action padding (12dp) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a99a220..b58638c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1650,6 +1650,8 @@
<!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings -->
<string-array name="fingerprint_error_vendor">
</string-array>
+ <!-- Default error message to use when fingerprint_error_vendor does not contain a message. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_error_vendor_unknown">Something went wrong. Try again.</string>
<!-- Content description which should be used for the fingerprint icon. -->
<string name="fingerprint_icon_content_description">Fingerprint icon</string>
@@ -1760,6 +1762,8 @@
<!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings -->
<string-array name="face_error_vendor">
</string-array>
+ <!-- Default error message to use when face_error_vendor does not contain a message. [CHAR LIMIT=NONE] -->
+ <string name="face_error_vendor_unknown">Something went wrong. Try again.</string>
<!-- Content description which should be used for the face icon. [CHAR LIMIT=10] -->
<string name="face_icon_content_description">Face icon</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bd10263..24da7b2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -439,6 +439,8 @@
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
<java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
<java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+ <java-symbol type="integer" name="config_longPressOnPowerDurationMs" />
+ <java-symbol type="array" name="config_longPressOnPowerDurationSettings" />
<java-symbol type="bool" name="config_longPressOnPowerForAssistantSettingAvailable" />
<java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
<java-symbol type="integer" name="config_veryLongPressTimeout" />
@@ -1742,6 +1744,7 @@
<java-symbol type="dimen" name="navigation_bar_width_car_mode" />
<java-symbol type="dimen" name="status_bar_height" />
<java-symbol type="dimen" name="display_cutout_touchable_region_size" />
+ <java-symbol type="dimen" name="system_gestures_start_threshold" />
<java-symbol type="dimen" name="quick_qs_offset_height" />
<java-symbol type="drawable" name="ic_jog_dial_sound_off" />
<java-symbol type="drawable" name="ic_jog_dial_sound_on" />
@@ -2524,6 +2527,7 @@
<java-symbol type="string" name="fingerprint_error_no_space" />
<java-symbol type="string" name="fingerprint_error_timeout" />
<java-symbol type="array" name="fingerprint_error_vendor" />
+ <java-symbol type="string" name="fingerprint_error_vendor_unknown" />
<java-symbol type="string" name="fingerprint_acquired_partial" />
<java-symbol type="string" name="fingerprint_acquired_insufficient" />
<java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
@@ -2563,6 +2567,7 @@
<java-symbol type="string" name="face_error_no_space" />
<java-symbol type="string" name="face_error_timeout" />
<java-symbol type="array" name="face_error_vendor" />
+ <java-symbol type="string" name="face_error_vendor_unknown" />
<java-symbol type="string" name="face_error_canceled" />
<java-symbol type="string" name="face_error_user_canceled" />
<java-symbol type="string" name="face_error_lockout" />
@@ -2605,6 +2610,7 @@
<java-symbol type="array" name="config_udfps_sensor_props" />
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
+ <java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
@@ -3184,6 +3190,7 @@
<java-symbol type="id" name="notification_action_list_margin_target" />
<java-symbol type="dimen" name="notification_actions_padding_start"/>
+ <java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/>
<java-symbol type="dimen" name="notification_action_disabled_alpha" />
<java-symbol type="id" name="tag_margin_end_when_icon_visible" />
<java-symbol type="id" name="tag_margin_end_when_icon_gone" />
@@ -4405,7 +4412,7 @@
<java-symbol type="string" name="view_and_control_notification_title" />
<java-symbol type="string" name="view_and_control_notification_content" />
<java-symbol type="array" name="config_accessibility_allowed_install_source" />
-
+
<!-- Translation -->
<java-symbol type="string" name="ui_translation_accessibility_translated_text" />
<java-symbol type="string" name="ui_translation_accessibility_translation_finished" />
@@ -4428,4 +4435,6 @@
<java-symbol type="bool" name="config_volumeShowRemoteSessions" />
<java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
+
+ <java-symbol type="color" name="overview_background"/>
</resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index cd07d46..34c1763 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,9 +16,11 @@
package android.app;
-import static androidx.core.graphics.ColorUtils.calculateContrast;
+import static android.app.Notification.Builder.ensureColorSpanContrast;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsAtLeast;
+import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsWithinRange;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +37,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@@ -42,12 +45,21 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.TextAppearanceSpan;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+import com.android.internal.util.ContrastColorUtil;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -334,6 +346,163 @@
}
@Test
+ public void testBuilder_getFullLengthSpanColor_returnsNullForString() {
+ assertThat(Notification.Builder.getFullLengthSpanColor("String")).isNull();
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_returnsNullWithPartialSpan() {
+ CharSequence text = new SpannableStringBuilder()
+ .append("text with ")
+ .append("some red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isNull();
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithSingleSpan() {
+ CharSequence text = new SpannableStringBuilder()
+ .append("text that is all red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.RED);
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithFullAndPartialSpans() {
+ Spannable text = new SpannableString("blue text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.BLUE);
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithTextAppearance() {
+ Spannable text = new SpannableString("title text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ int expectedTextColor = textAppearanceSpan.getTextColor().getDefaultColor();
+ text.setSpan(textAppearanceSpan, 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(expectedTextColor);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_removesAllFullLengthColorSpans() {
+ Spannable text = new SpannableString("blue text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ TextAppearanceSpan taSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ assertThat(taSpan.getTextColor()).isNotNull(); // it must be set to prove it is cleared.
+ text.setSpan(taSpan, 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spannable result = (Spannable) ensureColorSpanContrast(text, Color.BLACK);
+ Object[] spans = result.getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(3);
+
+ assertThat(result.getSpanStart(spans[0])).isEqualTo(15);
+ assertThat(result.getSpanEnd(spans[0])).isEqualTo(21);
+ assertThat(((ForegroundColorSpan) spans[0]).getForegroundColor()).isEqualTo(Color.YELLOW);
+
+ assertThat(result.getSpanStart(spans[1])).isEqualTo(0);
+ assertThat(result.getSpanEnd(spans[1])).isEqualTo(31);
+ assertThat(spans[1]).isNotSameInstanceAs(taSpan); // don't mutate the existing span
+ assertThat(((TextAppearanceSpan) spans[1]).getFamily()).isEqualTo(taSpan.getFamily());
+ assertThat(((TextAppearanceSpan) spans[1]).getTextColor()).isNull();
+
+ assertThat(result.getSpanStart(spans[2])).isEqualTo(26);
+ assertThat(result.getSpanEnd(spans[2])).isEqualTo(31);
+ assertThat(((ForegroundColorSpan) spans[2]).getForegroundColor()).isEqualTo(Color.GREEN);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_partialLength_adjusted() {
+ int background = 0xFFFF0101; // Slightly lighter red
+ CharSequence text = new SpannableStringBuilder()
+ .append("text with ")
+ .append("some red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ CharSequence result = ensureColorSpanContrast(text, background);
+
+ // ensure the span has been updated to have > 1.3:1 contrast ratio with fill color
+ Object[] spans = ((Spannable) result).getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(1);
+ int foregroundColor = ((ForegroundColorSpan) spans[0]).getForegroundColor();
+ assertContrastIsWithinRange(foregroundColor, background, 3, 3.2);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_worksWithComplexInput() {
+ Spannable text = new SpannableString("blue text with yellow and green and cyan");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // cyan TextAppearanceSpan
+ TextAppearanceSpan taSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ taSpan = new TextAppearanceSpan(taSpan.getFamily(), taSpan.getTextStyle(),
+ taSpan.getTextSize(), ColorStateList.valueOf(Color.CYAN), null);
+ text.setSpan(taSpan, 36, 40,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spannable result = (Spannable) ensureColorSpanContrast(text, Color.GRAY);
+ Object[] spans = result.getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(3);
+
+ assertThat(result.getSpanStart(spans[0])).isEqualTo(15);
+ assertThat(result.getSpanEnd(spans[0])).isEqualTo(21);
+ assertThat(((ForegroundColorSpan) spans[0]).getForegroundColor()).isEqualTo(Color.YELLOW);
+
+ assertThat(result.getSpanStart(spans[1])).isEqualTo(36);
+ assertThat(result.getSpanEnd(spans[1])).isEqualTo(40);
+ assertThat(spans[1]).isNotSameInstanceAs(taSpan); // don't mutate the existing span
+ assertThat(((TextAppearanceSpan) spans[1]).getFamily()).isEqualTo(taSpan.getFamily());
+ ColorStateList newCyanList = ((TextAppearanceSpan) spans[1]).getTextColor();
+ assertThat(newCyanList).isNotNull();
+ assertContrastIsWithinRange(newCyanList.getDefaultColor(), Color.GRAY, 3, 3.2);
+
+ assertThat(result.getSpanStart(spans[2])).isEqualTo(26);
+ assertThat(result.getSpanEnd(spans[2])).isEqualTo(31);
+ int newGreen = ((ForegroundColorSpan) spans[2]).getForegroundColor();
+ assertThat(newGreen).isNotEqualTo(Color.GREEN);
+ assertContrastIsWithinRange(newGreen, Color.GRAY, 3, 3.2);
+ }
+
+ @Test
+ public void testBuilder_ensureButtonFillContrast_adjustsDarker() {
+ int background = Color.LTGRAY;
+ int foreground = Color.LTGRAY;
+ int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
+ assertContrastIsWithinRange(result, background, 1.3, 1.5);
+ assertThat(ContrastColorUtil.calculateLuminance(result))
+ .isLessThan(ContrastColorUtil.calculateLuminance(background));
+ }
+
+ @Test
+ public void testBuilder_ensureButtonFillContrast_adjustsLighter() {
+ int background = Color.DKGRAY;
+ int foreground = Color.DKGRAY;
+ int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
+ assertContrastIsWithinRange(result, background, 1.3, 1.5);
+ assertThat(ContrastColorUtil.calculateLuminance(result))
+ .isGreaterThan(ContrastColorUtil.calculateLuminance(background));
+ }
+
+ @Test
public void testColors_ensureColors_dayMode_producesValidPalette() {
Notification.Colors c = new Notification.Colors();
boolean colorized = false;
@@ -399,6 +568,8 @@
assertEquals(cDay.getSecondaryTextColor(), cNight.getSecondaryTextColor());
assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor());
assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor());
+ assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor());
+ assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor());
assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor());
assertEquals(cDay.getContrastColor(), cNight.getContrastColor());
assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha());
@@ -413,30 +584,26 @@
assertThat(c.getSecondaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+ assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+ assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getRippleAlpha()).isAtLeast(0x00);
assertThat(c.getRippleAlpha()).isAtMost(0xff);
- // Assert that various colors have sufficient contrast
+ // Assert that various colors have sufficient contrast with the background
assertContrastIsAtLeast(c.getPrimaryTextColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getSecondaryTextColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getPrimaryAccentColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getErrorColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getContrastColor(), c.getBackgroundColor(), 4.5);
- // This accent color is only used for emphasized buttons
+ // These colors are only used for emphasized buttons; they do not need contrast
assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1);
- }
+ assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1);
- private void assertContrastIsAtLeast(int foreground, int background, double minContrast) {
- try {
- assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast);
- } catch (AssertionError e) {
- throw new AssertionError(
- String.format("Insufficient contrast: foreground=#%08x background=#%08x",
- foreground, background), e);
- }
+ // The text that is used within the accent color DOES need to have contrast
+ assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
}
private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor,
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 22f6ec0..701e619 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -27,6 +27,7 @@
import static junit.framework.Assert.fail;
+import android.graphics.fonts.FontCustomizationParser;
import android.graphics.fonts.FontStyle;
import android.os.LocaleList;
import android.text.FontConfig;
@@ -46,6 +47,7 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -318,6 +320,52 @@
}
}
+ @Test
+ public void alias() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + " <family name='custom-family'>"
+ + " <font>missing.ttf</font>"
+ + " </family>"
+ + " <alias name='custom-alias' to='sans-serif'/>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.Alias> aliases = config.getAliases();
+ assertThat(aliases.size()).isEqualTo(1);
+ assertThat(aliases.get(0).getName()).isEqualTo("custom-alias");
+ assertThat(aliases.get(0).getOriginal()).isEqualTo("sans-serif");
+ }
+
+ @Test
+ public void dropped_FamilyAlias() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + " <family name='custom-family'>"
+ + " <font>missing.ttf</font>"
+ + " </family>"
+ + " <alias name='custom-alias' to='custom-family'/>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, false /* exclude not existing file */);
+ assertThat(config.getAliases()).isEmpty();
+ }
+
+ private FontConfig readFamilies(String xml, boolean allowNonExisting)
+ throws IOException, XmlPullParserException {
+ ByteArrayInputStream buffer = new ByteArrayInputStream(
+ xml.getBytes(StandardCharsets.UTF_8));
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(buffer, "UTF-8");
+ parser.nextTag();
+ return FontListParser.readFamilies(parser, "", new FontCustomizationParser.Result(), null,
+ 0L /* last modified date */, 0 /* config version */, allowNonExisting);
+ }
+
private FontConfig.FontFamily readFamily(String xml)
throws IOException, XmlPullParserException {
ByteArrayInputStream buffer = new ByteArrayInputStream(
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index dfc9013..149f58f 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -32,6 +32,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -121,6 +122,46 @@
Mockito.verifyZeroInteractions(mListener);
}
+ @Test
+ public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreNoOtherListeners()
+ throws RemoteException {
+ mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+
+ }
+
+ @Test
+ public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
+ throws RemoteException {
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+ InOrder inOrder = Mockito.inOrder(mDisplayManager);
+
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(ALL_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.unregisterDisplayListener(mListener);
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+
+ }
+
private void waitForHandler() {
mHandler.runWithScissors(() -> { }, 0);
}
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index a5a98a9..109b7ab 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -17,6 +17,8 @@
package android.service.notification;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse;
@@ -51,6 +53,7 @@
private final Context mMockContext = mock(Context.class);
@Mock
+ private Context mRealContext;
private PackageManager mPm;
private static final String PKG = "com.example.o";
@@ -75,6 +78,8 @@
InstrumentationRegistry.getContext().getResources());
when(mMockContext.getPackageManager()).thenReturn(mPm);
when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+
+ mRealContext = InstrumentationRegistry.getContext();
}
@Test
@@ -199,6 +204,19 @@
}
+ @Test
+ public void testGetPackageContext_worksWithUserAll() {
+ String pkg = "com.android.systemui";
+ int uid = 1000;
+ Notification notification = getNotificationBuilder(GROUP_ID_1, CHANNEL_ID).build();
+ StatusBarNotification sbn = new StatusBarNotification(
+ pkg, pkg, ID, TAG, uid, uid, notification, UserHandle.ALL, null, UID);
+ Context resultContext = sbn.getPackageContext(mRealContext);
+ assertNotNull(resultContext);
+ assertNotSame(mRealContext, resultContext);
+ assertEquals(pkg, resultContext.getPackageName());
+ }
+
private StatusBarNotification getNotification(String pkg, String group, String channelId) {
return getNotification(pkg, getNotificationBuilder(group, channelId));
}
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 020f4a0..073e468 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -23,11 +23,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.content.res.Configuration;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.IWindowManager;
@@ -38,6 +40,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
/**
* Tests for {@link WindowContextController}
@@ -53,15 +57,18 @@
@Presubmit
public class WindowContextControllerTest {
private WindowContextController mController;
+ @Mock
private IWindowManager mMockWms;
+ @Mock
+ private WindowTokenClient mMockToken;
@Before
public void setUp() throws Exception {
- mMockWms = mock(IWindowManager.class);
- mController = new WindowContextController(new Binder(), mMockWms);
-
- doReturn(true).when(mMockWms).attachWindowContextToDisplayArea(any(), anyInt(),
- anyInt(), any());
+ MockitoAnnotations.initMocks(this);
+ mController = new WindowContextController(mMockToken, mMockWms);
+ doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt());
+ doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(),
+ anyInt(), anyInt(), any());
}
@Test(expected = IllegalStateException.class)
@@ -85,6 +92,7 @@
null /* options */);
assertThat(mController.mAttachedToDisplayArea).isTrue();
+ verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY));
mController.detachIfNeeded();
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 464412f..d4799a8 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -23,6 +23,8 @@
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.Uid.Sensor;
+import android.os.Process;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.util.SparseLongArray;
import android.view.Display;
@@ -53,6 +55,8 @@
public class BatteryStatsNoteTest extends TestCase {
private static final int UID = 10500;
+ private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
+ private static final int ISOLATED_UID = UserHandle.getUid(0, ISOLATED_APP_ID);
private static final WorkSource WS = new WorkSource(UID);
/**
@@ -114,6 +118,88 @@
assertEquals(120_000, bgTime);
}
+ /**
+ * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid.
+ */
+ @SmallTest
+ public void testNoteStartWakeLocked_isolatedUid() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ int pid = 10;
+ String name = "name";
+ String historyName = "historyName";
+
+ WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+ isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+ // Map ISOLATED_UID to UID.
+ bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+ bi.noteStartWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL, false);
+
+ clocks.realtime = clocks.uptime = 100;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ clocks.realtime = clocks.uptime = 220;
+ bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL);
+
+ // ISOLATED_UID wakelock time should be attributed to UID.
+ BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+ .getAggregatedPartialWakelockTimer();
+ long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ assertEquals(220_000, actualTime);
+ assertEquals(120_000, bgTime);
+ }
+
+ /**
+ * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid, with a race where the
+ * isolated uid is removed from batterystats before the wakelock has been stopped.
+ */
+ @SmallTest
+ public void testNoteStartWakeLocked_isolatedUidRace() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ int pid = 10;
+ String name = "name";
+ String historyName = "historyName";
+
+ WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+ isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+ // Map ISOLATED_UID to UID.
+ bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+ bi.noteStartWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL, false);
+
+ clocks.realtime = clocks.uptime = 100;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ clocks.realtime = clocks.uptime = 150;
+ bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime);
+
+ clocks.realtime = clocks.uptime = 220;
+ bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL);
+
+ // ISOLATED_UID wakelock time should be attributed to UID.
+ BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+ .getAggregatedPartialWakelockTimer();
+ long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ assertEquals(220_000, actualTime);
+ assertEquals(120_000, bgTime);
+ }
+
/**
* Test BatteryStatsImpl.noteUidProcessStateLocked.
@@ -506,7 +592,7 @@
public void testUpdateDisplayMeasuredEnergyStatsLocked() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
clocks.realtime = 0;
int screen = Display.STATE_OFF;
@@ -591,7 +677,7 @@
public void testUpdateCustomMeasuredEnergyStatsLocked_neverCalled() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
bi.setOnBatteryInternal(true);
final int uid1 = 11500;
@@ -605,7 +691,7 @@
public void testUpdateCustomMeasuredEnergyStatsLocked() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
final int bucketA = 0; // Custom bucket 0
final int bucketB = 1; // Custom bucket 1
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index b851f0a..0135fe8 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -403,7 +403,7 @@
assertNotNull(sensor.getSensorBackgroundTime());
// Reset the stats. Since the sensor is still running, we should still see the timer
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
sensor = uid.getSensorStats().get(SENSOR_ID);
assertNotNull(sensor);
@@ -413,7 +413,7 @@
bi.noteStopSensorLocked(UID, SENSOR_ID);
// Now the sensor timer has stopped so this reset should also take out the sensor.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
sensor = uid.getSensorStats().get(SENSOR_ID);
assertNull(sensor);
@@ -465,7 +465,7 @@
// Reset the stats. Since the sensor is still running, we should still see the timer
// but still with 0 times.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
assertEquals(0, timer.getTotalTimeLocked(1000*clocks.realtime, which));
assertEquals(0, timer.getTotalDurationMsLocked(clocks.realtime));
assertEquals(0, bgTimer.getTotalTimeLocked(1000*clocks.realtime, which));
@@ -504,7 +504,7 @@
// Reset the stats. Since the sensor is still running, we should still see the timer
// but with 0 times.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
assertEquals(0, timer.getTotalTimeLocked(1000*clocks.realtime, which));
assertEquals(0, timer.getTotalDurationMsLocked(clocks.realtime));
assertEquals(0, bgTimer.getTotalTimeLocked(1000*clocks.realtime, which));
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 0147cdb..74b6dbe 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -18,6 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.app.ActivityManager;
import android.content.Context;
import android.os.BatteryConsumer;
@@ -263,6 +266,39 @@
.of(180.0);
}
+ @Test
+ public void testAggregateBatteryStats_incompatibleSnapshot() {
+ Context context = InstrumentationRegistry.getContext();
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
+
+ BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class);
+
+ when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps())
+ .thenReturn(new long[]{1000, 2000});
+
+ when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn(
+ new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
+ .setStatsDuration(1234).build());
+
+ // Add a snapshot, with a different set of custom power components. It should
+ // be skipped by the aggregation.
+ when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn(
+ new BatteryUsageStats.Builder(new String[]{"different"})
+ .setStatsDuration(4321).build());
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
+ batteryStats, batteryUsageStatsStore);
+
+ BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+ .aggregateSnapshots(0, 3000)
+ .build();
+ final BatteryUsageStats stats = provider.getBatteryUsageStats(query);
+ assertThat(stats.getCustomPowerComponentNames())
+ .isEqualTo(batteryStats.getCustomEnergyConsumerNames());
+ assertThat(stats.getStatsDuration()).isEqualTo(1234);
+ }
+
private static class TestHandler extends Handler {
TestHandler() {
super(Looper.getMainLooper());
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 99d576d..cee1a03 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -57,11 +57,10 @@
this(new MockClocks());
}
- public void initMeasuredEnergyStats() {
+ public void initMeasuredEnergyStats(String[] customBucketNames) {
final boolean[] supportedStandardBuckets =
new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
Arrays.fill(supportedStandardBuckets, true);
- final String[] customBucketNames = {"FOO", "BAR"};
mGlobalMeasuredEnergyStats =
new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
}
diff --git a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
new file mode 100644
index 0000000..cfe660c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.util;
+
+import static androidx.core.graphics.ColorUtils.calculateContrast;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+
+import androidx.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+public class ContrastColorUtilTest extends TestCase {
+
+ @SmallTest
+ public void testEnsureTextContrastAgainstDark() {
+ int darkBg = 0xFF35302A;
+
+ int blueContrastColor = ContrastColorUtil.ensureTextContrast(Color.BLUE, darkBg, true);
+ assertContrastIsWithinRange(blueContrastColor, darkBg, 4.5, 4.75);
+
+ int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, darkBg, true);
+ assertContrastIsWithinRange(redContrastColor, darkBg, 4.5, 4.75);
+
+ final int darkGreen = 0xff008800;
+ int greenContrastColor = ContrastColorUtil.ensureTextContrast(darkGreen, darkBg, true);
+ assertContrastIsWithinRange(greenContrastColor, darkBg, 4.5, 4.75);
+
+ int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.DKGRAY, darkBg, true);
+ assertContrastIsWithinRange(grayContrastColor, darkBg, 4.5, 4.75);
+
+ int selfContrastColor = ContrastColorUtil.ensureTextContrast(darkBg, darkBg, true);
+ assertContrastIsWithinRange(selfContrastColor, darkBg, 4.5, 4.75);
+ }
+
+ @SmallTest
+ public void testEnsureTextContrastAgainstLight() {
+ int lightBg = 0xFFFFF8F2;
+
+ final int lightBlue = 0xff8888ff;
+ int blueContrastColor = ContrastColorUtil.ensureTextContrast(lightBlue, lightBg, false);
+ assertContrastIsWithinRange(blueContrastColor, lightBg, 4.5, 4.75);
+
+ int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, lightBg, false);
+ assertContrastIsWithinRange(redContrastColor, lightBg, 4.5, 4.75);
+
+ int greenContrastColor = ContrastColorUtil.ensureTextContrast(Color.GREEN, lightBg, false);
+ assertContrastIsWithinRange(greenContrastColor, lightBg, 4.5, 4.75);
+
+ int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.LTGRAY, lightBg, false);
+ assertContrastIsWithinRange(grayContrastColor, lightBg, 4.5, 4.75);
+
+ int selfContrastColor = ContrastColorUtil.ensureTextContrast(lightBg, lightBg, false);
+ assertContrastIsWithinRange(selfContrastColor, lightBg, 4.5, 4.75);
+ }
+
+ public static void assertContrastIsWithinRange(int foreground, int background,
+ double minContrast, double maxContrast) {
+ assertContrastIsAtLeast(foreground, background, minContrast);
+ assertContrastIsAtMost(foreground, background, maxContrast);
+ }
+
+ public static void assertContrastIsAtLeast(int foreground, int background, double minContrast) {
+ try {
+ assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast);
+ } catch (AssertionError e) {
+ throw new AssertionError(
+ String.format("Insufficient contrast: foreground=#%08x background=#%08x",
+ foreground, background), e);
+ }
+ }
+
+ public static void assertContrastIsAtMost(int foreground, int background, double maxContrast) {
+ try {
+ assertThat(calculateContrast(foreground, background)).isAtMost(maxContrast);
+ } catch (AssertionError e) {
+ throw new AssertionError(
+ String.format("Excessive contrast: foreground=#%08x background=#%08x",
+ foreground, background), e);
+ }
+ }
+
+}
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 40dda65..eeb65ae 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -48,6 +48,7 @@
<permission name="android.permission.SET_ACTIVITY_WATCHER"/>
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.car.permission.CONTROL_APP_BLOCKING"/>
<!-- use for rotary fragment to enable/disable packages related to rotary -->
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 93a336e..96b3325 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -25,6 +25,7 @@
import android.os.Build;
import android.os.LocaleList;
import android.text.FontConfig;
+import android.util.ArraySet;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -37,6 +38,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -120,7 +122,23 @@
}
}
- private static FontConfig readFamilies(
+ /**
+ * Parses the familyset tag in font.xml
+ * @param parser a XML pull parser
+ * @param fontDir A system font directory, e.g. "/system/fonts"
+ * @param customization A OEM font customization
+ * @param updatableFontMap A map of updated font files
+ * @param lastModifiedDate A date that the system font is updated.
+ * @param configVersion A version of system font config.
+ * @param allowNonExistingFile true if allowing non-existing font files during parsing fonts.xml
+ * @return result of fonts.xml
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ *
+ * @hide
+ */
+ public static FontConfig readFamilies(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@NonNull FontCustomizationParser.Result customization,
@@ -159,7 +177,24 @@
}
families.addAll(oemNamedFamilies.values());
- return new FontConfig(families, aliases, lastModifiedDate, configVersion);
+
+ // Filters aliases that point to non-existing families.
+ Set<String> namedFamilies = new ArraySet<>();
+ for (int i = 0; i < families.size(); ++i) {
+ String name = families.get(i).getName();
+ if (name != null) {
+ namedFamilies.add(name);
+ }
+ }
+ List<FontConfig.Alias> filtered = new ArrayList<>();
+ for (int i = 0; i < aliases.size(); ++i) {
+ FontConfig.Alias alias = aliases.get(i);
+ if (namedFamilies.contains(alias.getOriginal())) {
+ filtered.add(alias);
+ }
+ }
+
+ return new FontConfig(families, filtered, lastModifiedDate, configVersion);
}
private static boolean keepReading(XmlPullParser parser)
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 884d27f..9feb619 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -827,7 +827,7 @@
case RAW_SENSOR:
return 16;
case YCBCR_P010:
- return 20;
+ return 24;
case RAW_DEPTH10:
case RAW10:
return 10;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c0df06f..ac97c8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -576,20 +576,17 @@
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
- if (!mPositioner.showingInTaskbar()) {
- // Also, save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack(
- mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
- } else {
+ if (mPositioner.showingInTaskbar()) {
// In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
mMagnetizedObject = null;
+ } else {
+ // Save the magnetized stack so we can dispatch touch events to it.
+ mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
+ mMagnetizedObject.clearAllTargets();
+ mMagnetizedObject.addTarget(mMagneticTarget);
+ mMagnetizedObject.setMagnetListener(mStackMagnetListener);
}
- // Also, save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-
mIsDraggingStack = true;
// Cancel animations to make the stack temporarily invisible, since we're now
@@ -881,7 +878,6 @@
mRelativeStackPositionBeforeRotation = null;
}
- setUpDismissView();
if (mIsExpanded) {
// Re-draw bubble row and pointer for new orientation.
beforeExpandedViewAnimation();
@@ -1043,10 +1039,9 @@
contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */);
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
- // MagnetizedObjects.
+ // MagnetizedObjects when the dismiss view gets shown.
mMagneticTarget = new MagnetizedObject.MagneticTarget(
mDismissView.getCircle(), dismissRadius);
-
mBubbleContainer.bringToFront();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 0802fb5..636e145 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -1024,11 +1024,9 @@
}
/**
- * Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided
- * {@link MagnetizedObject.MagneticTarget} added as a target.
+ * Returns the {@link MagnetizedObject} instance for the bubble stack.
*/
- public MagnetizedObject<StackAnimationController> getMagnetizedStack(
- MagnetizedObject.MagneticTarget target) {
+ public MagnetizedObject<StackAnimationController> getMagnetizedStack() {
if (mMagnetizedStack == null) {
mMagnetizedStack = new MagnetizedObject<StackAnimationController>(
mLayout.getContext(),
@@ -1053,7 +1051,6 @@
loc[1] = (int) mStackPosition.y;
}
};
- mMagnetizedStack.addTarget(target);
mMagnetizedStack.setHapticsEnabled(true);
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index 9f6dd1f..9e01259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -303,6 +303,13 @@
}
/**
+ * Removes all associated targets from this object.
+ */
+ fun clearAllTargets() {
+ associatedTargets.clear()
+ }
+
+ /**
* Provide this method with all motion events that move the magnetized object. If the
* location of the motion events moves within the magnetic field of a target, or indicate a
* fling-to-target gesture, this method will return true and you should not move the object
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index d327470..9e1c61a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -23,6 +23,7 @@
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.view.ContextThemeWrapper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.animation.LinearInterpolator;
@@ -33,7 +34,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.view.ContextThemeWrapper;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 954ca14..e511bff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -445,6 +445,9 @@
mOneHandedSettingsUtil.registerSettingsKeyObserver(
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
}
private void unregisterSettingObservers() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 7cf4fb7..ff333c8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -171,9 +171,22 @@
* @return true if user enabled one-handed shortcut in settings, false otherwise.
*/
public boolean getShortcutEnabled(ContentResolver resolver, int userId) {
- final String targets = Settings.Secure.getStringForUser(resolver,
+ // Checks SOFTWARE_SHORTCUT_KEY
+ final String targetsSwKey = Settings.Secure.getStringForUser(resolver,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
- return TextUtils.isEmpty(targets) ? false : targets.contains(ONE_HANDED_MODE_TARGET_NAME);
+ if (!TextUtils.isEmpty(targetsSwKey) && targetsSwKey.contains(
+ ONE_HANDED_MODE_TARGET_NAME)) {
+ return true;
+ }
+
+ // Checks HARDWARE_SHORTCUT_KEY
+ final String targetsHwKey = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+ if (!TextUtils.isEmpty(targetsHwKey) && targetsHwKey.contains(
+ ONE_HANDED_MODE_TARGET_NAME)) {
+ return true;
+ }
+ return false;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index f58c6b1..81dd60d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -32,6 +32,7 @@
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -44,7 +45,6 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.appcompat.view.ContextThemeWrapper;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4f3ec96..63f1985 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -303,13 +303,13 @@
mOneHandedController = oneHandedController;
mPipTransitionController = pipTransitionController;
mTaskStackListener = taskStackListener;
- mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
- INPUT_CONSUMER_PIP, mainExecutor);
//TODO: move this to ShellInit when PipController can be injected
mMainExecutor.execute(this::init);
}
public void init() {
+ mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
+ INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipBoundsState.setDisplayId(displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
index a5e96d1..20021eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -28,6 +28,7 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
+import android.util.Log;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -45,7 +46,7 @@
class SizeCompatUILayout {
private static final String TAG = "SizeCompatUILayout";
- private final SyncTransactionQueue mSyncQueue;
+ final SyncTransactionQueue mSyncQueue;
private final SizeCompatUIController.SizeCompatUICallback mCallback;
private Context mContext;
private Configuration mTaskConfig;
@@ -306,6 +307,10 @@
private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) {
mSyncQueue.runInSync(t -> {
+ if (!leash.isValid()) {
+ Log.w(TAG, "The leash has been released.");
+ return;
+ }
t.setPosition(leash, positionX, positionY);
// The size compat UI should be the topmost child of the Task in case there can be more
// than one children.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
index f634c45..82f69c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
@@ -110,7 +110,8 @@
}
if (mLeash != null) {
- new SurfaceControl.Transaction().remove(mLeash).apply();
+ final SurfaceControl leash = mLeash;
+ mLayout.mSyncQueue.runInSync(t -> t.remove(leash));
mLeash = null;
}
}
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 109b535..3544987 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -50,6 +50,7 @@
bool Properties::skipEmptyFrames = true;
bool Properties::useBufferAge = true;
bool Properties::enablePartialUpdates = true;
+bool Properties::enableRenderEffectCache = false;
DebugLevel Properties::debugLevel = kDebugDisabled;
OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 7df6e2c..d224a54 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -224,6 +224,7 @@
static bool skipEmptyFrames;
static bool useBufferAge;
static bool enablePartialUpdates;
+ static bool enableRenderEffectCache;
// TODO: Move somewhere else?
static constexpr float textGamma = 1.45f;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 0c422df..b348a6e 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -341,6 +341,7 @@
sk_sp<SkImage> snapshot = layerSurface->makeImageSnapshot();
const auto subset = SkIRect::MakeWH(properties().getWidth(),
properties().getHeight());
+ uint32_t layerSurfaceGenerationId = layerSurface->generationID();
// If we don't have an ImageFilter just return the snapshot
if (imageFilter == nullptr) {
mSnapshotResult.snapshot = snapshot;
@@ -348,9 +349,10 @@
mSnapshotResult.outOffset = SkIPoint::Make(0.0f, 0.0f);
mImageFilterClipBounds = clipBounds;
mTargetImageFilter = nullptr;
- } else if (mSnapshotResult.snapshot == nullptr ||
- imageFilter != mTargetImageFilter.get() ||
- mImageFilterClipBounds != clipBounds) {
+ mTargetImageFilterLayerSurfaceGenerationId = 0;
+ } else if (mSnapshotResult.snapshot == nullptr || imageFilter != mTargetImageFilter.get() ||
+ mImageFilterClipBounds != clipBounds ||
+ mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) {
// Otherwise create a new snapshot with the given filter and snapshot
mSnapshotResult.snapshot =
snapshot->makeWithFilter(context,
@@ -361,6 +363,7 @@
&mSnapshotResult.outOffset);
mTargetImageFilter = sk_ref_sp(imageFilter);
mImageFilterClipBounds = clipBounds;
+ mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId;
}
return mSnapshotResult;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 45a4f6c..da04762 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -396,6 +396,7 @@
* SkImageFilter used to create the mSnapshotResult
*/
sk_sp<SkImageFilter> mTargetImageFilter;
+ uint32_t mTargetImageFilterLayerSurfaceGenerationId = 0;
/**
* Clip bounds used to create the mSnapshotResult
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 7556af9..48145d2 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -231,14 +231,33 @@
SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
SkPaint paint;
layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
- const auto snapshotResult = renderNode->updateSnapshotIfRequired(
- canvas->recordingContext(),
- layerProperties.getImageFilter(),
- clipBounds.roundOut()
- );
- sk_sp<SkImage> snapshotImage = snapshotResult->snapshot;
- srcBounds = snapshotResult->outSubset;
- offset = snapshotResult->outOffset;
+ sk_sp<SkImage> snapshotImage;
+ auto* imageFilter = layerProperties.getImageFilter();
+ auto recordingContext = canvas->recordingContext();
+ // On some GL vendor implementations, caching the result of
+ // getLayerSurface->makeImageSnapshot() causes a call to
+ // Fence::waitForever without a corresponding signal. This would
+ // lead to ANRs throughout the system.
+ // Instead only cache the SkImage created with the SkImageFilter
+ // for supported devices. Otherwise just create a new SkImage with
+ // the corresponding SkImageFilter each time.
+ // See b/193145089 and b/197263715
+ if (!Properties::enableRenderEffectCache) {
+ snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
+ if (imageFilter) {
+ auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height());
+ snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter,
+ subset, clipBounds.roundOut(),
+ &srcBounds, &offset);
+ }
+ } else {
+ const auto snapshotResult = renderNode->updateSnapshotIfRequired(
+ recordingContext, layerProperties.getImageFilter(), clipBounds.roundOut());
+ snapshotImage = snapshotResult->snapshot;
+ srcBounds = snapshotResult->outSubset;
+ offset = snapshotResult->outOffset;
+ }
+
const auto dstBounds = SkIRect::MakeXYWH(offset.x(),
offset.y(),
srcBounds.width(),
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index a116781..383c79b 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -146,6 +146,9 @@
LOG_ALWAYS_FATAL("Unsupported wide color space.");
}
mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension;
+
+ auto* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
+ Properties::enableRenderEffectCache = (strcmp(vendor, "Qualcomm") != 0);
}
EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) {
diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl
index 6b754e1..9b49123 100644
--- a/media/java/android/media/IMediaRouterClient.aidl
+++ b/media/java/android/media/IMediaRouterClient.aidl
@@ -23,5 +23,4 @@
void onStateChanged();
void onRestoreRoute();
void onGroupRouteSelected(String routeId);
- void onGlobalA2dpChanged(boolean a2dpOn);
}
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 345d9b2..2986f7c 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -654,9 +654,12 @@
final class Client extends IMediaRouterClient.Stub {
@Override
public void onStateChanged() {
- mHandler.post(() -> {
- if (Client.this == mClient) {
- updateClientState();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (Client.this == mClient) {
+ updateClientState();
+ }
}
});
}
@@ -690,26 +693,6 @@
}
});
}
-
- // Called when the selection of a connected device (phone speaker or BT devices)
- // is changed.
- @Override
- public void onGlobalA2dpChanged(boolean a2dpOn) {
- mHandler.post(() -> {
- if (mSelectedRoute == null || mBluetoothA2dpRoute == null) {
- return;
- }
- if (mSelectedRoute.isDefault() && a2dpOn) {
- setSelectedRoute(mBluetoothA2dpRoute, /*explicit=*/ false);
- dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
- dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
- } else if (mSelectedRoute.isBluetooth() && !a2dpOn) {
- setSelectedRoute(mDefaultAudioVideo, /*explicit=*/ false);
- dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
- dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
- }
- });
- }
}
}
@@ -1367,9 +1350,6 @@
}
static void dispatchRouteSelected(int type, RouteInfo info) {
- if (DEBUG) {
- Log.d(TAG, "Dispatching route selected: " + info);
- }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteSelected(cbi.router, type, info);
@@ -1378,9 +1358,6 @@
}
static void dispatchRouteUnselected(int type, RouteInfo info) {
- if (DEBUG) {
- Log.d(TAG, "Dispatching route unselected: " + info);
- }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteUnselected(cbi.router, type, info);
diff --git a/mms/OWNERS b/mms/OWNERS
index 7f05a2a..2e419c1 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -9,10 +9,10 @@
jminjie@google.com
satk@google.com
shuoq@google.com
-nazaninb@google.com
sarahchin@google.com
xiaotonj@google.com
huiwang@google.com
jayachandranc@google.com
chinmayd@google.com
amruthr@google.com
+sasindran@google.com
diff --git a/packages/CarrierDefaultApp/OWNERS b/packages/CarrierDefaultApp/OWNERS
index 0d23f05..a2352e2 100644
--- a/packages/CarrierDefaultApp/OWNERS
+++ b/packages/CarrierDefaultApp/OWNERS
@@ -8,11 +8,11 @@
jminjie@google.com
satk@google.com
shuoq@google.com
-nazaninb@google.com
sarahchin@google.com
xiaotonj@google.com
huiwang@google.com
jayachandranc@google.com
chinmayd@google.com
amruthr@google.com
+sasindran@google.com
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 48cdf16..197b7b2 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -31,7 +31,7 @@
android:directBootAware="true">
<receiver android:name=".TemporaryFileManager"
- android:exported="true">
+ android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
@@ -76,7 +76,7 @@
<receiver android:name=".InstallEventReceiver"
android:permission="android.permission.INSTALL_PACKAGES"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" />
</intent-filter>
@@ -106,14 +106,14 @@
<receiver android:name=".UninstallEventReceiver"
android:permission="android.permission.INSTALL_PACKAGES"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="com.android.packageinstaller.ACTION_UNINSTALL_COMMIT" />
</intent-filter>
</receiver>
<receiver android:name=".PackageInstalledReceiver"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="android.intent.action.PACKAGE_ADDED" />
<data android:scheme="package" />
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 5d7b9bb..cef9014 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -19,6 +19,9 @@
},
{
"name": "CtsPackageUninstallTestCases"
+ },
+ {
+ "name": "PackageInstallerTests"
}
]
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 72fa25f..bf0dc7b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -157,7 +157,6 @@
private Network mDefaultNetwork = null;
private NetworkCapabilities mDefaultNetworkCapabilities = null;
private final Runnable mCallback;
- private final boolean mSupportMergedUi;
private WifiInfo mWifiInfo;
public boolean enabled;
@@ -181,7 +180,6 @@
mNetworkScoreManager = networkScoreManager;
mConnectivityManager = connectivityManager;
mCallback = callback;
- mSupportMergedUi = false;
}
public void setListening(boolean listening) {
@@ -223,10 +221,8 @@
} else {
ssid = getValidSsid(mWifiInfo);
}
- if (mSupportMergedUi) {
- isCarrierMerged = mWifiInfo.isCarrierMerged();
- subId = mWifiInfo.getSubscriptionId();
- }
+ isCarrierMerged = mWifiInfo.isCarrierMerged();
+ subId = mWifiInfo.getSubscriptionId();
updateRssi(mWifiInfo.getRssi());
maybeRequestNetworkScore();
}
@@ -255,10 +251,8 @@
} else {
ssid = getValidSsid(mWifiInfo);
}
- if (mSupportMergedUi) {
- isCarrierMerged = mWifiInfo.isCarrierMerged();
- subId = mWifiInfo.getSubscriptionId();
- }
+ isCarrierMerged = mWifiInfo.isCarrierMerged();
+ subId = mWifiInfo.getSubscriptionId();
updateRssi(mWifiInfo.getRssi());
maybeRequestNetworkScore();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 6100615..56454e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -20,10 +20,13 @@
import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiInfo;
+import android.os.Bundle;
import android.os.SystemClock;
import androidx.annotation.VisibleForTesting;
@@ -36,6 +39,23 @@
private static final int INVALID_RSSI = -127;
+ /**
+ * The intent action shows network details settings to allow configuration of Wi-Fi.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: The calling package should put the chosen
+ * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
+ * the {@link #KEY_CHOSEN_WIFIENTRY_KEY}.
+ * <p>
+ * Output: Nothing.
+ */
+ public static final String ACTION_WIFI_DETAILS_SETTINGS =
+ "android.settings.WIFI_DETAILS_SETTINGS";
+ public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
+ public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
+
static final int[] WIFI_PIE = {
com.android.internal.R.drawable.ic_wifi_signal_0,
com.android.internal.R.drawable.ic_wifi_signal_1,
@@ -275,7 +295,42 @@
return noInternet ? NO_INTERNET_WIFI_PIE[level] : WIFI_PIE[level];
}
+ /**
+ * Wrapper the {@link #getInternetIconResource} for testing compatibility.
+ */
+ public static class InternetIconInjector {
+
+ protected final Context mContext;
+
+ public InternetIconInjector(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Returns the Internet icon for a given RSSI level.
+ *
+ * @param noInternet True if a connected Wi-Fi network cannot access the Internet
+ * @param level The number of bars to show (0-4)
+ */
+ public Drawable getIcon(boolean noInternet, int level) {
+ return mContext.getDrawable(WifiUtils.getInternetIconResource(level, noInternet));
+ }
+ }
+
public static boolean isMeteredOverridden(WifiConfiguration config) {
return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE;
}
+
+ /**
+ * Returns the Intent for Wi-Fi network details settings.
+ *
+ * @param key The Wi-Fi entry key
+ */
+ public static Intent getWifiDetailsSettingsIntent(String key) {
+ final Intent intent = new Intent(ACTION_WIFI_DETAILS_SETTINGS);
+ final Bundle bundle = new Bundle();
+ bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
+ return intent;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 89960cb..7c2b904 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -20,9 +20,12 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
import android.net.NetworkKey;
import android.net.RssiCurve;
import android.net.ScoredNetwork;
@@ -36,6 +39,8 @@
import android.text.format.DateUtils;
import android.util.ArraySet;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.settingslib.R;
import org.junit.Before;
@@ -44,7 +49,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Set;
@@ -69,7 +73,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = spy(ApplicationProvider.getApplicationContext());
}
@Test
@@ -148,6 +152,32 @@
assertThat(WifiUtils.isMeteredOverridden(mWifiConfig)).isTrue();
}
+ @Test
+ public void getWifiDetailsSettingsIntent_returnsCorrectValues() {
+ final String key = "test_key";
+
+ final Intent intent = WifiUtils.getWifiDetailsSettingsIntent(key);
+
+ assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DETAILS_SETTINGS);
+ final Bundle bundle = intent.getBundleExtra(WifiUtils.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(bundle.getString(WifiUtils.KEY_CHOSEN_WIFIENTRY_KEY)).isEqualTo(key);
+ }
+
+ @Test
+ public void testInternetIconInjector_getIcon_returnsCorrectValues() {
+ WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
+
+ for (int level = 0; level <= 4; level++) {
+ iconInjector.getIcon(false /* noInternet */, level);
+ verify(mContext).getDrawable(
+ WifiUtils.getInternetIconResource(level, false /* noInternet */));
+
+ iconInjector.getIcon(true /* noInternet */, level);
+ verify(mContext).getDrawable(
+ WifiUtils.getInternetIconResource(level, true /* noInternet */));
+ }
+ }
+
private static ArrayList<ScanResult> buildScanResultCache() {
ArrayList<ScanResult> scanResults = new ArrayList<>();
for (int i = 0; i < 5; i++) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index eb81961..a46d28b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -76,5 +76,6 @@
Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
Settings.Global.DEVICE_CONFIG_SYNC_DISABLED,
Settings.Global.POWER_BUTTON_LONG_PRESS,
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 6022608..96f127b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -101,6 +101,7 @@
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
Settings.Secure.QS_TILES,
+ Settings.Secure.QS_AUTO_ADDED_TILES,
Settings.Secure.CONTROLS_ENABLED,
Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT,
Settings.Secure.DOZE_ENABLED,
@@ -118,7 +119,6 @@
Settings.Secure.VR_DISPLAY_MODE,
Settings.Secure.NOTIFICATION_BADGING,
Settings.Secure.NOTIFICATION_DISMISS_RTL,
- Settings.Secure.QS_AUTO_ADDED_TILES,
Settings.Secure.SCREENSAVER_ENABLED,
Settings.Secure.SCREENSAVER_COMPONENTS,
Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 5220a04..84c5feb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -20,6 +20,7 @@
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR;
import static android.view.Display.HdrCapabilities.HDR_TYPES;
@@ -140,6 +141,7 @@
/* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
VALIDATORS.put(Global.DISABLE_WINDOW_BLURS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.DEVICE_CONFIG_SYNC_DISABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index c577868..6cfcb51 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -72,8 +72,9 @@
* {@hide}
*/
private static final ArraySet<String> sBroadcastOnRestore;
+ private static final ArraySet<String> sBroadcastOnRestoreSystemUI;
static {
- sBroadcastOnRestore = new ArraySet<String>(4);
+ sBroadcastOnRestore = new ArraySet<String>(9);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
@@ -83,6 +84,9 @@
sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+ sBroadcastOnRestoreSystemUI = new ArraySet<String>(2);
+ sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES);
+ sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES);
}
private interface SettingsLookup {
@@ -133,6 +137,7 @@
// Will we need a post-restore broadcast for this element?
String oldValue = null;
boolean sendBroadcast = false;
+ boolean sendBroadcastSystemUI = false;
final SettingsLookup table;
if (destination.equals(Settings.Secure.CONTENT_URI)) {
@@ -143,10 +148,12 @@
table = sGlobalLookup;
}
- if (sBroadcastOnRestore.contains(name)) {
+ sendBroadcast = sBroadcastOnRestore.contains(name);
+ sendBroadcastSystemUI = sBroadcastOnRestoreSystemUI.contains(name);
+
+ if (sendBroadcast || sendBroadcastSystemUI) {
// TODO: http://b/22388012
oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM);
- sendBroadcast = true;
}
try {
@@ -193,18 +200,28 @@
} catch (Exception e) {
// If we fail to apply the setting, by definition nothing happened
sendBroadcast = false;
+ sendBroadcastSystemUI = false;
} finally {
// If this was an element of interest, send the "we just restored it"
// broadcast with the historical value now that the new value has
// been committed and observers kicked off.
- if (sendBroadcast) {
+ if (sendBroadcast || sendBroadcastSystemUI) {
Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
- .setPackage("android").addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
.putExtra(Intent.EXTRA_SETTING_NAME, name)
.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value)
.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue)
.putExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, restoredFromSdkInt);
- context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+
+ if (sendBroadcast) {
+ intent.setPackage("android");
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+ }
+ if (sendBroadcastSystemUI) {
+ intent.setPackage(
+ context.getString(com.android.internal.R.string.config_systemUi));
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+ }
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 073b4d0..4ac1938 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -763,9 +763,6 @@
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES);
dumpSetting(s, p,
- Settings.Global.ANGLE_ALLOWLIST,
- GlobalSettingsProto.Gpu.ANGLE_ALLOWLIST);
- dumpSetting(s, p,
Settings.Global.ANGLE_EGL_FEATURES,
GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES);
dumpSetting(s, p,
@@ -1195,6 +1192,9 @@
dumpSetting(s, p,
Settings.Global.POWER_MANAGER_CONSTANTS,
GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
+ GlobalSettingsProto.POWER_BUTTON_LONG_PRESS_DURATION_MS);
final long prepaidSetupToken = p.start(GlobalSettingsProto.PREPAID_SETUP);
dumpSetting(s, p,
@@ -1476,6 +1476,9 @@
Settings.Global.USE_OPEN_WIFI_PACKAGE,
GlobalSettingsProto.USE_OPEN_WIFI_PACKAGE);
dumpSetting(s, p,
+ Settings.Global.UWB_ENABLED,
+ GlobalSettingsProto.UWB_ENABLED);
+ dumpSetting(s, p,
Settings.Global.VT_IMS_ENABLED,
GlobalSettingsProto.VT_IMS_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3297937..7db73c6 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -509,7 +509,6 @@
Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE,
Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS,
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
- Settings.Global.ANGLE_ALLOWLIST,
Settings.Global.ANGLE_EGL_FEATURES,
Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
@@ -519,6 +518,7 @@
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
+ Settings.Global.UWB_ENABLED,
Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX,
Settings.Global.GPU_DEBUG_LAYER_APP,
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml b/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
new file mode 100644
index 0000000..495fbb8
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="24dp"/>
+ <gradient
+ android:angle="0"
+ android:startColor="#00000000"
+ android:endColor="#ff000000"
+ android:type="linear" />
+</shape>
diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml
index a4d0ff7..1934457 100644
--- a/packages/SystemUI/res-keyguard/values/donottranslate.xml
+++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml
@@ -21,6 +21,9 @@
<!-- Skeleton string format for displaying the date when an alarm is set. -->
<string name="abbrev_wday_month_day_no_year_alarm">EEEMMMd</string>
+ <!-- Skeleton string format for displaying the date shorter. -->
+ <string name="abbrev_month_day_no_year">MMMd</string>
+
<!-- Skeleton string format for displaying the time in 12-hour format. -->
<string name="clock_12hr_format">hm</string>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 72b027a..098b7e8 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -142,4 +142,8 @@
<item name="android:shadowColor">@color/keyguard_shadow_color</item>
<item name="android:shadowRadius">?attr/shadowRadius</item>
</style>
+
+ <style name="TextAppearance.Keyguard.BottomArea.Button">
+ <item name="android:shadowRadius">0</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml
new file mode 100644
index 0000000..13133cb
--- /dev/null
+++ b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<!-- Copy of progress_indeterminate_horizontal_rect2 in frameworks/base/core/res -->
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="2000"
+ android:propertyXName="translateX"
+ android:pathData="M -197.60001,0 c 14.28182,0 85.07782,0 135.54689,0 c 54.26191,0 90.42461,0 168.24331,0 c 144.72154,0 316.40982,0 316.40982,0 "
+ android:interpolator="@interpolator/progress_indeterminate_horizontal_rect2_translatex_copy"
+ android:repeatCount="infinite" />
+</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_state_off.xml b/packages/SystemUI/res/color/settingslib_state_off.xml
new file mode 100644
index 0000000..e821825
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_state_off.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondaryVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_state_on.xml b/packages/SystemUI/res/color/settingslib_state_on.xml
new file mode 100644
index 0000000..6d2133c
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_state_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_track_off.xml b/packages/SystemUI/res/color/settingslib_track_off.xml
new file mode 100644
index 0000000..21d1dcc
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_track_off.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_track_on.xml b/packages/SystemUI/res/color/settingslib_track_on.xml
new file mode 100644
index 0000000..ba7848a
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_track_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_arrow_forward.xml b/packages/SystemUI/res/drawable/ic_arrow_forward.xml
new file mode 100644
index 0000000..438e4c7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_forward.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:autoMirrored="true"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M6.23,20.23l1.77,1.77l10,-10l-10,-10l-1.77,1.77l8.23,8.23z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml
new file mode 100644
index 0000000..2c34060
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10C20,8.9 19.1,8 18,8zM9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2H9V6zM18,20H6V10h12V20zM12,17c1.1,0 2,-0.9 2,-2c0,-1.1 -0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2C10,16.1 10.9,17 12,17z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_home_devices.xml b/packages/SystemUI/res/drawable/ic_media_home_devices.xml
new file mode 100644
index 0000000..886c64d9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_home_devices.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4c-1.1,0 -2,0.9 -2,2v11c0,1.1 0.9,2 2,2h4v2h3v-4H4V6h16v1h2V6c0,-1.1 -0.9,-2 -2,-2z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17.5,16.5m-2.33,0a2.33,2.33 0,1 1,4.66 0a2.33,2.33 0,1 1,-4.66 0"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M21,8h-7c-0.55,0 -1,0.45 -1,1v11c0,0.55 0.45,1 1,1h7c0.55,0 1,-0.45 1,-1L22,9c0,-0.55 -0.45,-1 -1,-1zM17.5,9c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5 -1.5,-0.67 -1.5,-1.5 0.67,-1.5 1.5,-1.5zM17.5,20c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings_24dp.xml b/packages/SystemUI/res/drawable/ic_settings_24dp.xml
new file mode 100644
index 0000000..ac4c43b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_settings_24dp.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml
new file mode 100644
index 0000000..f38a368
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22l16,0l0,-2l-11,0l13,-13l0,1l2,0l0,-6z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,10h2v8h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,20h2v2h-2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_background.xml b/packages/SystemUI/res/drawable/internet_dialog_background.xml
new file mode 100644
index 0000000..3ceb0f6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <shape android:shape="rectangle">
+ <corners android:radius="8dp" />
+ <solid android:color="?android:attr/colorBackground" />
+ </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
new file mode 100644
index 0000000..50267fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"
+ android:width="1dp"/>
+ <corners android:radius="20dp"/>
+ <padding
+ android:left="8dp"
+ android:right="8dp"
+ android:top="4dp"
+ android:bottom="4dp" />
+ <solid android:color="@android:color/transparent" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
new file mode 100644
index 0000000..14672ef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/internet_dialog_corner_radius"
+ android:topRightRadius="@dimen/internet_dialog_corner_radius"
+ android:bottomLeftRadius="@dimen/internet_dialog_corner_radius"
+ android:bottomRightRadius="@dimen/internet_dialog_corner_radius"/>
+ <solid android:color="?android:attr/colorBackground" />
+ </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/logout_button_background.xml b/packages/SystemUI/res/drawable/logout_button_background.xml
index eafd663..34434be 100644
--- a/packages/SystemUI/res/drawable/logout_button_background.xml
+++ b/packages/SystemUI/res/drawable/logout_button_background.xml
@@ -17,7 +17,8 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="@color/logout_button_bg_color"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
<corners android:radius="@dimen/logout_button_corner_radius"/>
</shape>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
index 6725a26..e626675 100644
--- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
@@ -14,19 +14,20 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <solid
- android:color="?androidprv:attr/colorSurface"
- />
- <corners android:radius="20dp" />
-
- <padding
- android:left="20dp"
- android:right="20dp">
- </padding>
-
-</shape>
\ No newline at end of file
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <inset
+ android:insetBottom="4dp"
+ android:insetTop="4dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="20dp" />
+ <padding
+ android:left="20dp"
+ android:right="20dp" />
+ <solid android:color="?androidprv:attr/colorSurface" />
+ </shape>
+ </inset>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
new file mode 100644
index 0000000..95209f8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<!-- Copy of progress_indeterminate_horizontal_material_trimmed in frameworks/base/core/res -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/vector_drawable_progress_indeterminate_horizontal_trimmed" >
+ <target
+ android:name="rect_grp"
+ android:animation="@anim/progress_indeterminate_horizontal_rect" />
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml
new file mode 100644
index 0000000..088e82b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/settingslib_state_off_color"/>
+ <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
new file mode 100644
index 0000000..250188b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/settingslib_state_on_color"/>
+ <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml
new file mode 100644
index 0000000..b41762f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid
+ android:color="@color/settingslib_thumb_off_color"
+ android:alpha="?android:attr/disabledAlpha"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_off.xml b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml
new file mode 100644
index 0000000..87d4aea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid android:color="@color/settingslib_thumb_off_color"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
new file mode 100644
index 0000000..5566ea3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid android:color="@color/settingslib_state_on_color"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml
new file mode 100644
index 0000000..06bb779
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/settingslib_thumb_on" android:state_checked="true"/>
+ <item android:drawable="@drawable/settingslib_thumb_off" android:state_checked="false"/>
+ <item android:drawable="@drawable/settingslib_thumb_disabled" android:state_enabled="false"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml
new file mode 100644
index 0000000..15dfcb7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <solid
+ android:color="@color/settingslib_track_off_color"
+ android:alpha="?android:attr/disabledAlpha"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_off_background.xml b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
new file mode 100644
index 0000000..3a09284
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <padding android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"/>
+ <solid android:color="@color/settingslib_track_off_color"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
new file mode 100644
index 0000000..1d9dacd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <padding android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"/>
+ <solid android:color="@color/settingslib_track_on_color"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_selector.xml b/packages/SystemUI/res/drawable/settingslib_track_selector.xml
new file mode 100644
index 0000000..a38c3b4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/settingslib_track_on_background" android:state_checked="true"/>
+ <item android:drawable="@drawable/settingslib_track_off_background" android:state_checked="false"/>
+ <item android:drawable="@drawable/settingslib_track_disabled_background" android:state_enabled="false"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
new file mode 100644
index 0000000..aec204f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<!-- Variant of vector_drawable_progress_indeterminate_horizontal in frameworks/base/core/res, which
+ draws the whole height of the progress bar instead having blank space above and below the
+ bar. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:height="10dp"
+ android:width="340dp"
+ android:viewportHeight="10"
+ android:viewportWidth="340" >
+ <group
+ android:name="progress_group"
+ android:translateX="180"
+ android:translateY="5" >
+ <path
+ android:name="background_track"
+ android:pathData="M -180.0,-5.0 l 360.0,0 l 0,10.0 l -360.0,0 Z"
+ android:fillColor="?androidprv:attr/colorSurfaceVariant"/>
+ <group
+ android:name="rect_grp"
+ android:translateX="-197.60001"
+ android:scaleX="0.5" >
+ <path
+ android:name="rect"
+ android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"
+ android:fillColor="?androidprv:attr/colorAccentPrimaryVariant" />
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_toast.xml b/packages/SystemUI/res/layout/global_actions_toast.xml
new file mode 100644
index 0000000..1f08996
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_toast.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center|bottom"
+ android:gravity="center"
+ android:layout_marginBottom="@dimen/global_actions_info_margin"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="382dp"
+ android:layout_weight="0"
+ android:background="@drawable/global_actions_lite_background"
+ android:theme="@style/Theme.SystemUI.QuickSettings"
+ android:paddingTop="14dp"
+ android:paddingBottom="14dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textSize="14sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/global_action_smart_lock_disabled" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml
index e4a9694..6a9254c 100644
--- a/packages/SystemUI/res/layout/global_screenshot_static.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_static.xml
@@ -36,7 +36,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/screenshot_action_container_offset_y"
android:paddingEnd="@dimen/screenshot_action_container_padding_right"
android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
android:elevation="1dp"
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
new file mode 100644
index 0000000..918635d
--- /dev/null
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -0,0 +1,348 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/internet_connectivity_dialog"
+ android:layout_width="@dimen/internet_dialog_list_max_width"
+ android:layout_height="@dimen/internet_dialog_list_max_height"
+ android:background="@drawable/internet_dialog_rounded_top_corner_background"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Widget.SliceView.Panel"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="@dimen/internet_dialog_network_layout_margin"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/internet_dialog_title"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="32dp"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="24sp"/>
+
+ <TextView
+ android:id="@+id/internet_dialog_subtitle"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="20dp"
+ android:layout_marginTop="4dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearance.InternetDialog.Secondary"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/internet_dialog_network_layout_margin"
+ android:orientation="vertical">
+
+ <View
+ android:id="@+id/divider"
+ android:layout_gravity="center_vertical|center_horizontal"
+ android:layout_width="340dp"
+ android:layout_height="4dp"
+ android:background="?androidprv:attr/colorSurfaceVariant"/>
+
+ <ProgressBar
+ android:id="@+id/wifi_searching_progress"
+ android:indeterminate="true"
+ android:layout_width="340dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:visibility="gone"
+ style="@style/TrimmedHorizontalProgressBar"/>
+ </LinearLayout>
+
+ <androidx.core.widget.NestedScrollView
+ android:id="@+id/scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:id="@+id/scroll_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/ethernet_layout"
+ style="@style/InternetDialog.Network"
+ android:background="@drawable/settingslib_switch_bar_bg_on"
+ android:visibility="gone">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical|start"
+ android:clickable="false">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:autoMirrored="true"
+ android:src="@drawable/stat_sys_ethernet_fully"
+ android:tint="@color/connected_network_primary_color"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical"
+ android:orientation="vertical"
+ android:clickable="false">
+ <TextView
+ android:text="@string/ethernet_label"
+ style="@style/InternetDialog.NetworkTitle.Active"/>
+ <TextView
+ android:text="@string/to_switch_networks_disconnect_ethernet"
+ style="@style/InternetDialog.NetworkSummary.Active"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/mobile_network_layout"
+ style="@style/InternetDialog.Network">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/signal_icon"
+ android:autoMirrored="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/mobile_title"
+ style="@style/InternetDialog.NetworkTitle"/>
+ <TextView
+ android:id="@+id/mobile_summary"
+ style="@style/InternetDialog.NetworkSummary"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="48dp"
+ android:layout_gravity="end|center_vertical">
+ <Switch
+ android:id="@+id/mobile_toggle"
+ android:contentDescription="@string/mobile_data_settings_title"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="match_parent"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/turn_on_wifi_layout"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:gravity="center"
+ android:clickable="false"
+ android:focusable="false">
+
+ <FrameLayout
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/wifi_toggle_title"
+ android:text="@string/turn_on_wifi"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:textAppearance="@style/TextAppearance.InternetDialog"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="48dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp">
+ <Switch
+ android:id="@+id/wifi_toggle"
+ android:contentDescription="@string/turn_on_wifi"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="match_parent"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/wifi_connected_layout"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="24dp"
+ android:background="@drawable/settingslib_switch_bar_bg_on"
+ android:visibility="gone">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/wifi_connected_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="72dp"
+ android:layout_marginEnd="30dp"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/wifi_connected_title"
+ style="@style/InternetDialog.NetworkTitle.Active"
+ android:textSize="14sp"/>
+ <TextView
+ android:id="@+id/wifi_connected_summary"
+ style="@style/InternetDialog.NetworkSummary.Active"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="match_parent"
+ android:clickable="false"
+ android:layout_gravity="end|center_vertical"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/wifi_settings_icon"
+ android:src="@drawable/ic_settings_24dp"
+ android:layout_width="24dp"
+ android:layout_gravity="end|center_vertical"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/wifi_list_layout"
+ android:scrollbars="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:overScrollMode="never"
+ android:nestedScrollingEnabled="false"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/see_all_layout"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical|center_horizontal"
+ android:orientation="horizontal"
+ android:paddingStart="22dp"
+ android:paddingEnd="22dp">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ <ImageView
+ android:id="@+id/arrow_forward"
+ android:src="@drawable/ic_arrow_forward"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ <TextView
+ android:text="@string/see_all_networks"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="14sp"/>
+ </FrameLayout>
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/done_layout"
+ android:layout_width="67dp"
+ android:layout_height="48dp"
+ android:layout_marginEnd="24dp"
+ android:layout_marginBottom="40dp"
+ android:layout_gravity="end|center_vertical"
+ android:clickable="true"
+ android:focusable="true">
+ <Button
+ android:text="@string/inline_done_button"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="match_parent"
+ android:layout_height="36dp"
+ android:layout_gravity="center"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="14sp"
+ android:background="@drawable/internet_dialog_footer_background"
+ android:clickable="false"/>
+ </FrameLayout>
+ </LinearLayout>
+ </androidx.core.widget.NestedScrollView>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
new file mode 100644
index 0000000..868331e
--- /dev/null
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/internet_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/wifi_list"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="24dp">
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/wifi_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/wifi_network_layout"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="72dp"
+ android:layout_marginEnd="30dp"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/wifi_title"
+ style="@style/InternetDialog.NetworkTitle"
+ android:textSize="14sp"/>
+ <TextView
+ android:id="@+id/wifi_summary"
+ style="@style/InternetDialog.NetworkSummary"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="match_parent"
+ android:clickable="false"
+ android:layout_gravity="end|center_vertical">
+ <ImageView
+ android:id="@+id/wifi_end_icon"
+ android:layout_gravity="end|center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index a21a63c..9cf09ff 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -15,19 +15,25 @@
~ limitations under the License
-->
<!-- This is a view that shows a user switcher in Keyguard. -->
-<com.android.systemui.statusbar.phone.UserAvatarView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyguard_qs_user_switch_view"
- android:layout_width="@dimen/kg_framed_avatar_size"
- android:layout_height="@dimen/kg_framed_avatar_size"
- android:layout_centerHorizontal="true"
- android:layout_gravity="top|end"
- android:layout_marginEnd="16dp"
- systemui:avatarPadding="0dp"
- systemui:badgeDiameter="18dp"
- systemui:badgeMargin="1dp"
- systemui:frameColor="@color/kg_user_avatar_frame"
- systemui:framePadding="0dp"
- systemui:frameWidth="0dp">
-</com.android.systemui.statusbar.phone.UserAvatarView>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end">
+ <com.android.systemui.statusbar.phone.UserAvatarView
+ android:id="@+id/kg_multi_user_avatar"
+ android:layout_width="@dimen/kg_framed_avatar_size"
+ android:layout_height="@dimen/kg_framed_avatar_size"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="16dp"
+ systemui:avatarPadding="0dp"
+ systemui:badgeDiameter="18dp"
+ systemui:badgeMargin="1dp"
+ systemui:frameColor="@color/kg_user_avatar_frame"
+ systemui:framePadding="0dp"
+ systemui:frameWidth="0dp">
+ </com.android.systemui.statusbar.phone.UserAvatarView>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index eb76382..850b017 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -30,7 +30,6 @@
android:id="@+id/status_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:paddingEnd="@dimen/system_icons_keyguard_padding_end"
android:paddingTop="@dimen/status_bar_padding_top"
android:layout_alignParentEnd="true"
android:gravity="center_vertical|end" >
@@ -39,6 +38,7 @@
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginStart="@dimen/system_icons_super_container_margin_start"
+ android:layout_marginEnd="@dimen/status_bar_padding_end"
android:gravity="center_vertical|end">
<include layout="@layout/system_icons" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index c7e54d4..c3fc669 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -48,20 +48,41 @@
android:id="@+id/recommendation_card_icon"
android:layout_width="@dimen/qs_media_icon_size"
android:layout_height="@dimen/qs_media_icon_size"
+ android:layout_marginTop="@dimen/qs_media_padding"
android:src="@drawable/ic_headset"
- style="@style/MediaPlayer.AppIcon"/>
+ style="@style/MediaPlayer.AppIcon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="0"/>
<TextView
android:id="@+id/recommendation_card_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:maxLines="2"
+ android:maxLines="1"
android:text="@string/controls_media_smartspace_rec_title"
android:fontFamily="google-sans-medium"
android:textDirection="locale"
android:textSize="@dimen/qq_aa_media_rec_header_text_size"
- android:breakStrategy="balanced"
- android:hyphenationFrequency="none"/>
+ android:hyphenationFrequency="none"
+ app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <View
+ android:id="@+id/recommendation_gradient_view"
+ android:layout_width="@dimen/qs_aa_media_gradient_bg_width"
+ android:layout_height="0dp"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:background="@drawable/qs_media_recommendation_bg_gradient"
+ app:layout_constraintTop_toTopOf="@id/recommendation_card_text"
+ app:layout_constraintBottom_toBottomOf="@id/recommendation_card_text"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="1"/>
<FrameLayout
android:id="@+id/media_cover1_container"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 075473a..566cd25 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -163,18 +163,6 @@
</LinearLayout>
</LinearLayout>
- <ImageView
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:tint="?android:attr/textColor"
- android:src="@drawable/ic_cast_connected"
- android:forceHasOverlappingRendering="false" />
-
<!-- Seek Bar -->
<!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
<SeekBar
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index 459fb66..ee4530c 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -24,9 +24,9 @@
android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
android:layout_marginTop="8dp"
android:orientation="vertical"
- android:paddingBottom="12dp"
- android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:paddingTop="12dp"
+ android:paddingHorizontal="@dimen/ongoing_appops_dialog_side_padding"
android:background="@drawable/qs_dialog_bg"
/>
-<!-- 12dp padding bottom so there's 20dp total under the icon -->
-<!-- 8dp padding top, as there's 4dp margin in each row -->
\ No newline at end of file
+<!-- 8dp padding bottom so there's 16dp total under the icon -->
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml
index 7c8945e..e1f0793 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_item.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml
@@ -24,8 +24,6 @@
android:layout_marginTop="4dp"
android:importantForAccessibility="yes"
android:background="?android:attr/selectableItemBackground"
- android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
- android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
android:focusable="true"
>
<!-- 4dp marginTop makes 20dp minimum between icons -->
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 5b9ca1b..42a7c89 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -26,17 +26,38 @@
android:focusable="true"
android:theme="@style/Theme.SystemUI.QuickSettings.Header">
- <com.android.systemui.statusbar.policy.Clock
- android:id="@+id/clock"
+ <LinearLayout
+ android:id="@+id/clock_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="48dp"
- android:minHeight="48dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center_vertical|start"
android:gravity="center_vertical|start"
- android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
- android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.QS.Status" />
+ >
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minHeight="48dp"
+ android:gravity="center_vertical|start"
+ android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status" />
+
+ <com.android.systemui.statusbar.policy.VariableDateView
+ android:id="@+id/date_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/status_bar_left_clock_end_padding"
+ android:gravity="center_vertical|start"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status"
+ systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ systemui:shortDatePattern="@string/abbrev_month_day_no_year"
+ />
+ </LinearLayout>
<include layout="@layout/qs_carrier_group"
android:id="@+id/carrier_group"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index bff93a9..b1e8c38 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -36,7 +36,7 @@
android:layout_weight="1"
android:gravity="center_vertical|start" >
- <com.android.systemui.statusbar.policy.DateView
+ <com.android.systemui.statusbar.policy.VariableDateView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -44,9 +44,16 @@
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.QS.Status"
- systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+ systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ systemui:shortDatePattern="@string/abbrev_month_day_no_year"
+ />
</FrameLayout>
+ <!-- We want this to be centered (to align with notches). In order to do that, the following
+ has to hold (in portrait):
+ * date_container and privacy_container must have the same width and weight
+ * header_text_container must be gone
+ -->
<android.widget.Space
android:id="@+id/space"
android:layout_width="0dp"
@@ -73,7 +80,7 @@
android:layout_weight="1"
android:gravity="center_vertical|end" >
- <include layout="@layout/ongoing_privacy_chip" />
+ <include layout="@layout/ongoing_privacy_chip" />
</FrameLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index d1cc01f..c122829 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -113,6 +113,8 @@
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="center_vertical"
android:text="@string/screenrecord_taps_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="@*android:string/config_headlineFontFamily"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index 412276d..bbb8df1c 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -30,8 +30,8 @@
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/manage_text"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginTop="16dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
android:layout_gravity="start"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
@@ -43,8 +43,8 @@
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginTop="16dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
android:layout_gravity="end"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index bea50e8..71e8fc9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -62,8 +62,7 @@
<com.android.systemui.statusbar.LightRevealScrim
android:id="@+id/light_reveal_scrim"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone" />
+ android:layout_height="match_parent" />
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 19ec8ce..f057603 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -16,7 +16,7 @@
<resources>
<!-- Minimum margin between clock and top of screen or ambient indication -->
- <dimen name="keyguard_clock_top_margin">76dp</dimen>
+ <dimen name="keyguard_clock_top_margin">38dp</dimen>
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 461505f..ffcc3a8 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -48,4 +48,16 @@
<item name="android:windowLightStatusBar">false</item>
</style>
+ <style name="TextAppearance.InternetDialog.Active">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/connected_network_primary_color</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Secondary.Active">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@color/connected_network_secondary_color</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index da80b85..0a34dfd 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -95,4 +95,7 @@
<dimen name="controls_top_margin">24dp</dimen>
<dimen name="global_actions_grid_item_layout_height">80dp</dimen>
+
+ <!-- Internet panel related dimensions -->
+ <dimen name="internet_dialog_list_max_width">624dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index b5337d3..3121ce3 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -69,6 +69,10 @@
<declare-styleable name="DateView">
<attr name="datePattern" format="string" />
</declare-styleable>
+ <declare-styleable name="VariableDateView">
+ <attr name="longDatePattern" format="string" />
+ <attr name="shortDatePattern" format="string" />
+ </declare-styleable>
<declare-styleable name="PseudoGridView">
<attr name="numColumns" format="integer" />
<attr name="verticalSpacing" format="dimension" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 2260d21..f539b0c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-->
-<resources>
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<drawable name="notification_number_text_color">#ffffffff</drawable>
<drawable name="ticker_background_color">#ff1d1d1d</drawable>
<drawable name="system_bar_background">@color/system_bar_background_opaque</drawable>
@@ -187,10 +187,8 @@
<!-- UDFPS colors -->
<color name="udfps_enroll_icon">#000000</color> <!-- 100% black -->
<color name="udfps_moving_target_fill">#cc4285f4</color> <!-- 80% blue -->
- <color name="udfps_enroll_progress">#ff669DF6</color> <!-- 100% blue -->
-
- <!-- Logout button -->
- <color name="logout_button_bg_color">#ccffffff</color>
+ <color name="udfps_enroll_progress">#ff669DF6</color> <!-- blue 400 -->
+ <color name="udfps_enroll_progress_help">#ffEE675C</color> <!-- red 400 -->
<!-- Color for the Assistant invocation lights -->
<color name="default_invocation_lights_color">#ffffffff</color> <!-- white -->
@@ -281,4 +279,16 @@
<color name="wallet_card_border">#33FFFFFF</color>
<color name="people_tile_background">@android:color/system_accent2_50</color>
+
+ <!-- Internet Dialog -->
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_state_off</color>
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_track_on</color>
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_track_off</color>
+ <color name="connected_network_primary_color">#191C18</color>
+ <color name="connected_network_secondary_color">#41493D</color>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 78db2a8..c231afc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -90,7 +90,7 @@
<dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
<!-- End padding for left-aligned status bar clock -->
- <dimen name="status_bar_left_clock_end_padding">7dp</dimen>
+ <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
<!-- Spacing after the wifi signals that is present if there are any icons following it. -->
<dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
@@ -331,11 +331,10 @@
<dimen name="global_screenshot_x_scale">80dp</dimen>
<dimen name="screenshot_bg_protection_height">242dp</dimen>
<dimen name="screenshot_preview_elevation">4dp</dimen>
- <dimen name="screenshot_offset_y">24dp</dimen>
+ <dimen name="screenshot_offset_y">8dp</dimen>
<dimen name="screenshot_offset_x">16dp</dimen>
<dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
<dimen name="screenshot_dismiss_button_margin">8dp</dimen>
- <dimen name="screenshot_action_container_offset_y">16dp</dimen>
<dimen name="screenshot_action_container_corner_radius">18dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
@@ -744,9 +743,7 @@
<!-- The margin between the status view and the notifications on Keyguard.-->
<dimen name="keyguard_status_view_bottom_margin">20dp</dimen>
<!-- Minimum margin between clock and status bar -->
- <dimen name="keyguard_clock_top_margin">36dp</dimen>
- <!-- The margin between top of clock and bottom of lock icon. -->
- <dimen name="keyguard_clock_lock_margin">16dp</dimen>
+ <dimen name="keyguard_clock_top_margin">18dp</dimen>
<!-- The amount to shift the clocks during a small/large transition -->
<dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
@@ -1150,9 +1147,9 @@
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
<!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
- direction that elements aer moved to prevent burn-in on AOD-->
- <dimen name="udfps_burn_in_offset_x">2dp</dimen>
- <dimen name="udfps_burn_in_offset_y">8dp</dimen>
+ direction that elements are moved to prevent burn-in on AOD-->
+ <dimen name="udfps_burn_in_offset_x">7px</dimen>
+ <dimen name="udfps_burn_in_offset_y">28px</dimen>
<dimen name="corner_size">8dp</dimen>
<dimen name="top_padding">0dp</dimen>
@@ -1243,10 +1240,7 @@
<integer name="wired_charging_keyguard_text_animation_distance">-30</integer>
<!-- Logout button -->
- <dimen name="logout_button_layout_height">32dp</dimen>
- <dimen name="logout_button_padding_horizontal">16dp</dimen>
- <dimen name="logout_button_margin_bottom">12dp</dimen>
- <dimen name="logout_button_corner_radius">4dp</dimen>
+ <dimen name="logout_button_corner_radius">50dp</dimen>
<!-- Blur radius on status bar window and power menu -->
<dimen name="min_window_blur_radius">1px</dimen>
@@ -1277,7 +1271,7 @@
<dimen name="ongoing_appops_dialog_circle_size">32dp</dimen>
- <dimen name="ongoing_appops_dialog_icon_size">20dp</dimen>
+ <dimen name="ongoing_appops_dialog_icon_size">16dp</dimen>
<dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
@@ -1299,8 +1293,6 @@
<dimen name="qs_media_action_margin">12dp</dimen>
<dimen name="qs_seamless_height">24dp</dimen>
<dimen name="qs_seamless_icon_size">12dp</dimen>
- <dimen name="qs_seamless_fallback_icon_size">@dimen/qs_seamless_icon_size</dimen>
- <dimen name="qs_seamless_fallback_margin">20dp</dimen>
<dimen name="qs_footer_horizontal_margin">22dp</dimen>
<dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
<dimen name="qs_media_enabled_seekbar_height">2dp</dimen>
@@ -1310,6 +1302,7 @@
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_aa_media_rec_album_size_collapsed">72dp</dimen>
<dimen name="qs_aa_media_rec_album_size_expanded">76dp</dimen>
+ <dimen name="qs_aa_media_gradient_bg_width">32dp</dimen>
<dimen name="qs_aa_media_rec_album_margin">8dp</dimen>
<dimen name="qs_aa_media_rec_album_margin_vert">4dp</dimen>
<dimen name="qq_aa_media_rec_header_text_size">16sp</dimen>
@@ -1590,4 +1583,42 @@
<!-- The padding between the icon and the text. -->
<dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
<dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+
+ <!-- Internet panel related dimensions -->
+ <dimen name="internet_dialog_list_margin">12dp</dimen>
+ <dimen name="internet_dialog_list_max_height">646dp</dimen>
+ <dimen name="internet_dialog_list_max_width">@dimen/match_parent</dimen>
+
+ <!-- Signal icon in internet dialog -->
+ <dimen name="signal_strength_icon_size">24dp</dimen>
+
+ <!-- Internet dialog related dimensions -->
+ <dimen name="internet_dialog_corner_radius">24dp</dimen>
+ <!-- End margin of network layout -->
+ <dimen name="internet_dialog_network_layout_margin">16dp</dimen>
+ <!-- Size of switch bar in internet dialog -->
+ <dimen name="settingslib_switchbar_margin">16dp</dimen>
+ <!-- Minimum width of switch -->
+ <dimen name="settingslib_min_switch_width">52dp</dimen>
+ <!-- Size of layout margin left -->
+ <dimen name="settingslib_switchbar_padding_left">20dp</dimen>
+ <!-- Size of layout margin right -->
+ <dimen name="settingslib_switchbar_padding_right">20dp</dimen>
+ <!-- Radius of switch bar -->
+ <dimen name="settingslib_switch_bar_radius">35dp</dimen>
+ <!-- Margin of switch thumb -->
+ <dimen name="settingslib_switch_thumb_margin">4dp</dimen>
+ <!-- Size of switch thumb -->
+ <dimen name="settingslib_switch_thumb_size">20dp</dimen>
+ <!-- Width of switch track -->
+ <dimen name="settingslib_switch_track_width">52dp</dimen>
+ <!-- Height of switch track -->
+ <dimen name="settingslib_switch_track_height">28dp</dimen>
+ <!-- Radius of switch track -->
+ <dimen name="settingslib_switch_track_radius">35dp</dimen>
+
+ <!-- Height percentage of the parent container occupied by the communal view -->
+ <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
+
+ <dimen name="drag_and_drop_icon_size">70dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a670216..7aacb70 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -211,6 +211,8 @@
<!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
<string name="global_action_screenshot">Screenshot</string>
+ <!-- Message shown in power menu when smart lock has been disabled [CHAR_LIMIT=NONE] -->
+ <string name="global_action_smart_lock_disabled">Smart Lock disabled</string>
<!-- text to show in place of RemoteInput images when they cannot be shown.
[CHAR LIMIT=50] -->
@@ -1006,7 +1008,7 @@
<string name="sensor_privacy_start_use_mic_camera_dialog_content">This unblocks access for all apps and services allowed to use your camera or microphone.</string>
<!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
- <string name="media_seamless_remote_device">Device</string>
+ <string name="media_seamless_other_device">Other device</string>
<!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] -->
<string name="quick_step_accessibility_toggle_overview">Toggle Overview</string>
@@ -1777,6 +1779,8 @@
<string name="tuner_full_importance_settings">Power notification controls</string>
<string name="tuner_full_importance_settings_on">On</string>
<string name="tuner_full_importance_settings_off">Off</string>
+ <!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description -->
+ <string name="rotation_lock_camera_rotation_on">On - Face-based</string>
<string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications.
\n\n<b>Level 5</b>
\n- Show at the top of the notification list
@@ -2992,4 +2996,41 @@
<string name="global_actions_change_description" translatable="false"><xliff:g>%1$s</xliff:g></string>
<!-- URL for more information about changes in global actions -->
<string name="global_actions_change_url" translatable="false"></string>
+
+ <!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
+ <string name="mobile_data_settings_title">Mobile data</string>
+ <!-- Provider Model: Summary text separator for preferences including a short description
+ (eg. "Connected / 5G"). [CHAR LIMIT=50] -->
+ <string name="preference_summary_default_combination"><xliff:g id="state" example="Connected">%1$s</xliff:g> / <xliff:g id="networkMode" example="LTE">%2$s</xliff:g></string>
+ <!-- Provider Model:
+ Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
+ <string name="mobile_data_connection_active">Connected</string>
+ <!-- Provider Model:
+ Summary indicating that a SIM has no mobile data connection [CHAR LIMIT=50] -->
+ <string name="mobile_data_off_summary">Mobile data won\u0027t auto\u2011connect</string>
+ <!-- Provider Model:
+ Summary indicating that a active SIM and no network available [CHAR LIMIT=50] -->
+ <string name="mobile_data_no_connection">No connection</string>
+ <!-- Provider Model: Summary indicating that no other networks available [CHAR LIMIT=50] -->
+ <string name="non_carrier_network_unavailable">No other networks available</string>
+ <!-- Provider Model: Summary indicating that no networks available [CHAR LIMIT=50] -->
+ <string name="all_network_unavailable">No networks available</string>
+ <!-- Provider Model: Panel title text for turning on the Wi-Fi networks. [CHAR LIMIT=40] -->
+ <string name="turn_on_wifi">Wi\u2011Fi</string>
+ <!-- Provider Model: Title for detail page of wifi network [CHAR LIMIT=30] -->
+ <string name="pref_title_network_details" msgid="7329759534269363308">"Network details"</string>
+ <!-- Provider Model: Panel subtitle for tapping a network to connect to internet. [CHAR LIMIT=60] -->
+ <string name="tap_a_network_to_connect">Tap a network to connect</string>
+ <!-- Provider Model: Panel subtitle for unlocking screen to view networks. [CHAR LIMIT=60] -->
+ <string name="unlock_to_view_networks">Unlock to view networks</string>
+ <!-- Provider Model: Wi-Fi settings. text displayed when Wi-Fi is on and network list is empty [CHAR LIMIT=50]-->
+ <string name="wifi_empty_list_wifi_on">Searching for networks\u2026</string>
+ <!-- Provider Model: Failure notification for connect -->
+ <string name="wifi_failed_connect_message">Failed to connect to network</string>
+ <!-- Provider Model: Toast message for when the user selects cellular as the internet provider and Wi-Fi auto-connect is temporarily disabled [CHAR LIMIT=60]-->
+ <string name="wifi_wont_autoconnect_for_now">Wi\u2011Fi won\u2019t auto-connect for now</string>
+ <!-- Provider Model: Title to see all the networks [CHAR LIMIT=50] -->
+ <string name="see_all_networks">See all</string>
+ <!-- Summary for warning to disconnect ethernet first then switch to other networks. [CHAR LIMIT=60] -->
+ <string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 51eabf6..93d60cc 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -416,7 +416,9 @@
</style>
<!-- Overridden by values-television/styles.xml with tv-specific settings -->
- <style name="volume_dialog_theme" parent="Theme.SystemUI"/>
+ <style name="volume_dialog_theme" parent="Theme.SystemUI">
+ <item name="android:windowIsFloating">true</item>
+ </style>
<style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
@@ -903,4 +905,108 @@
<!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen. -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
</style>
+
+ <style name="Animation.InternetDialog" parent="@android:style/Animation.InputMethod">
+ </style>
+
+ <style name="Widget.SliceView.Panel">
+ <item name="titleSize">16sp</item>
+ <item name="rowStyle">@style/SliceRow</item>
+ <item name="android:background">?android:attr/colorBackgroundFloating</item>
+ </style>
+
+ <style name="SliceRow">
+ <!-- 2dp start padding for the start icon -->
+ <item name="titleItemStartPadding">2dp</item>
+ <item name="titleItemEndPadding">0dp</item>
+
+ <!-- Padding between content and the start icon is 14dp -->
+ <item name="contentStartPadding">14dp</item>
+ <!-- Padding between content and end items is 16dp -->
+ <item name="contentEndPadding">16dp</item>
+
+ <!-- Both side margins of end item are 16dp -->
+ <item name="endItemStartPadding">16dp</item>
+ <item name="endItemEndPadding">16dp</item>
+
+ <!-- Both side margins of bottom divider are 12dp -->
+ <item name="bottomDividerStartPadding">12dp</item>
+ <item name="bottomDividerEndPadding">12dp</item>
+
+ <item name="actionDividerHeight">32dp</item>
+ </style>
+
+ <style name="Theme.SystemUI.Dialog.Internet">
+ <item name="android:windowBackground">@drawable/internet_dialog_background</item>
+ </style>
+
+ <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item>
+ </style>
+
+ <style name="TrimmedHorizontalProgressBar"
+ parent="android:Widget.Material.ProgressBar.Horizontal">
+ <item name="android:indeterminateDrawable">
+ @drawable/progress_indeterminate_horizontal_material_trimmed
+ </item>
+ <item name="android:minHeight">4dp</item>
+ <item name="android:maxHeight">4dp</item>
+ </style>
+
+ <!-- Internet Dialog -->
+ <style name="InternetDialog">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center_vertical|start</item>
+ <item name="android:layout_marginStart">@dimen/internet_dialog_network_layout_margin</item>
+ </style>
+
+ <style name="InternetDialog.Network">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">88dp</item>
+ <item name="android:layout_marginEnd">@dimen/internet_dialog_network_layout_margin</item>
+ <item name="android:paddingStart">22dp</item>
+ <item name="android:paddingEnd">22dp</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:focusable">true</item>
+ <item name="android:clickable">true</item>
+ </style>
+
+ <style name="InternetDialog.NetworkTitle">
+ <item name="android:layout_marginEnd">7dp</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog</item>
+ </style>
+
+ <style name="InternetDialog.NetworkTitle.Active">
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Active</item>
+ </style>
+
+ <style name="InternetDialog.NetworkSummary">
+ <item name="android:layout_marginEnd">34dp</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary</item>
+ </style>
+
+ <style name="InternetDialog.NetworkSummary.Active">
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary.Active
+ </item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Secondary">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Active"/>
+
+ <style name="TextAppearance.InternetDialog.Secondary.Active"/>
+
</resources>
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index d6c6a60..c3510b6 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -44,23 +44,6 @@
/>
<Constraint
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:alpha="0.5"
- android:visibility="gone"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline"
- app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
- app:layout_constraintEnd_toEndOf="parent"
- />
-
- <Constraint
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size_small"
android:layout_height="@dimen/qs_media_album_size_small"
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index 0e284e6..6b83aae 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -45,22 +45,6 @@
android:layout_marginBottom="4dp" />
<Constraint
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="16dp"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:alpha="0.5"
- android:visibility="gone"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
- app:layout_constraintEnd_toEndOf="parent"
- />
-
- <Constraint
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size"
android:layout_height="@dimen/qs_media_album_size"
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
index 5c41ad8..b6258d1 100644
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
@@ -19,25 +19,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_icon_size"
- android:layout_height="@dimen/qs_media_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
- android:id="@+id/recommendation_card_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
android:id="@+id/media_cover1_container"
android:layout_width="0dp"
android:layout_height="@dimen/qs_aa_media_rec_album_size_collapsed"
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
index 8a3d5ca..2fb3341 100644
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
@@ -19,25 +19,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_icon_size"
- android:layout_height="@dimen/qs_media_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
- android:id="@+id/recommendation_card_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
android:id="@+id/media_cover1_container"
android:layout_width="0dp"
android:layout_height="@dimen/qs_aa_media_rec_album_size_expanded"
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 92f89d6..a383cab 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -20,12 +20,16 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.graphics.Color;
import android.icu.text.NumberFormat;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -67,20 +71,20 @@
BroadcastDispatcher broadcastDispatcher,
BatteryController batteryController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController bypassController) {
+ KeyguardBypassController bypassController,
+ @Main Resources resources
+ ) {
super(view);
mStatusBarStateController = statusBarStateController;
- mIsDozing = mStatusBarStateController.isDozing();
- mDozeAmount = mStatusBarStateController.getDozeAmount();
mBroadcastDispatcher = broadcastDispatcher;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mBypassController = bypassController;
mBatteryController = batteryController;
mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
- mBurmeseLineSpacing = getContext().getResources().getFloat(
+ mBurmeseLineSpacing = resources.getFloat(
R.dimen.keyguard_clock_line_spacing_scale_burmese);
- mDefaultLineSpacing = getContext().getResources().getFloat(
+ mDefaultLineSpacing = resources.getFloat(
R.dimen.keyguard_clock_line_spacing_scale);
}
@@ -106,7 +110,7 @@
}
};
- private final StatusBarStateController.StateListener mStatusBarStatePersistentListener =
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
@@ -144,11 +148,11 @@
mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
mDozeAmount = mStatusBarStateController.getDozeAmount();
+ mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
mBatteryController.addCallback(mBatteryCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
- mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
- mStatusBarStateController.addCallback(mStatusBarStatePersistentListener);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
refreshTime();
initColors();
@@ -160,9 +164,7 @@
mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mBatteryController.removeCallback(mBatteryCallback);
- if (!mView.isAttachedToWindow()) {
- mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
- }
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
/** Animate the clock appearance */
@@ -191,6 +193,14 @@
mView.refreshFormat();
}
+ /**
+ * Return locallly stored dozing state.
+ */
+ @VisibleForTesting
+ public boolean isDozing() {
+ return mIsDozing;
+ }
+
private void updateLocale() {
Locale currLocale = Locale.getDefault();
if (!Objects.equals(currLocale, mLocale)) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index f4a3fb2..7a01b4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -20,6 +20,7 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.WallpaperManager;
+import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
import android.widget.FrameLayout;
@@ -30,6 +31,7 @@
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -65,18 +67,22 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
+ private final Resources mResources;
/**
* Clock for both small and large sizes
*/
private AnimatableClockController mClockViewController;
- private FrameLayout mClockFrame;
+ private FrameLayout mClockFrame; // top aligned clock
private AnimatableClockController mLargeClockViewController;
- private FrameLayout mLargeClockFrame;
+ private FrameLayout mLargeClockFrame; // centered clock
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardBypassController mBypassController;
+ private int mLargeClockTopMargin = 0;
+ private int mKeyguardClockTopMargin = 0;
+
/**
* Listener for changes to the color palette.
*
@@ -113,7 +119,8 @@
KeyguardBypassController bypassController,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- SmartspaceTransitionController smartspaceTransitionController) {
+ SmartspaceTransitionController smartspaceTransitionController,
+ @Main Resources resources) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mColorExtractor = colorExtractor;
@@ -125,6 +132,7 @@
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mBypassController = bypassController;
mSmartspaceController = smartspaceController;
+ mResources = resources;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mSmartspaceTransitionController = smartspaceTransitionController;
@@ -154,7 +162,8 @@
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
- mBypassController);
+ mBypassController,
+ mResources);
mClockViewController.init();
mLargeClockViewController =
@@ -164,7 +173,8 @@
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
- mBypassController);
+ mBypassController,
+ mResources);
mLargeClockViewController.init();
}
@@ -175,6 +185,8 @@
}
mColorExtractor.addOnColorsChangedListener(mColorsListener);
mView.updateColors(getGradientColors());
+ mKeyguardClockTopMargin =
+ mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
View ksa = mView.findViewById(R.id.keyguard_status_area);
@@ -249,6 +261,8 @@
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
+ mKeyguardClockTopMargin =
+ mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
updateClockLayout();
}
@@ -257,9 +271,12 @@
if (mSmartspaceController.isEnabled()) {
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
MATCH_PARENT);
- lp.topMargin = getContext().getResources().getDimensionPixelSize(
+ mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize(
R.dimen.keyguard_large_clock_top_margin);
+ lp.topMargin = mLargeClockTopMargin;
mLargeClockFrame.setLayoutParams(lp);
+ } else {
+ mLargeClockTopMargin = 0;
}
}
@@ -369,6 +386,28 @@
}
}
+ /**
+ * Get y-bottom position of the currently visible clock on the keyguard.
+ * We can't directly getBottom() because clock changes positions in AOD for burn-in
+ */
+ int getClockBottom(int statusBarHeaderHeight) {
+ if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+ View clock = mLargeClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view_large);
+ int frameHeight = mLargeClockFrame.getHeight();
+ int clockHeight = clock.getHeight();
+ return frameHeight / 2 + clockHeight / 2;
+ } else {
+ return mClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view).getHeight()
+ + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ }
+ }
+
+ boolean isClockTopAligned() {
+ return mLargeClockFrame.getVisibility() != View.VISIBLE;
+ }
+
private void updateAodIcons() {
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 72e5028..6b3e9c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -185,6 +185,20 @@
}
/**
+ * Get y-bottom position of the currently visible clock.
+ */
+ public int getClockBottom(int statusBarHeaderHeight) {
+ return mKeyguardClockSwitchController.getClockBottom(statusBarHeaderHeight);
+ }
+
+ /**
+ * @return true if the currently displayed clock is top aligned (as opposed to center aligned)
+ */
+ public boolean isClockTopAligned() {
+ return mKeyguardClockSwitchController.isClockTopAligned();
+ }
+
+ /**
* Set whether the view accessibility importance mode.
*/
public void setStatusAccessibilityImportance(int mode) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 28a54d5..e115c34 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -132,8 +132,7 @@
.alpha(1f)
.withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
.start();
- } else if (mUnlockedScreenOffAnimationController
- .isScreenOffLightRevealAnimationPlaying()) {
+ } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) {
mKeyguardViewVisibilityAnimating = true;
// Ask the screen off animation controller to animate the keyguard visibility for us
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 5c34beb..ef4353b 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -27,6 +27,7 @@
import android.widget.ImageView;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
@@ -47,6 +48,7 @@
private ImageView mBgView;
private int mLockIconColor;
+ private boolean mUseBackground = false;
public LockIconView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -60,8 +62,8 @@
mBgView = findViewById(R.id.lock_icon_bg);
}
- void updateColorAndBackgroundVisibility(boolean useBackground) {
- if (useBackground && mLockIcon.getDrawable() != null) {
+ void updateColorAndBackgroundVisibility() {
+ if (mUseBackground && mLockIcon.getDrawable() != null) {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
@@ -77,6 +79,9 @@
void setImageDrawable(Drawable drawable) {
mLockIcon.setImageDrawable(drawable);
+
+ if (!mUseBackground) return;
+
if (drawable == null) {
mBgView.setVisibility(View.INVISIBLE);
} else {
@@ -84,6 +89,18 @@
}
}
+ /**
+ * Whether or not to render the lock icon background. Mainly used for UDPFS.
+ */
+ public void setUseBackground(boolean useBackground) {
+ mUseBackground = useBackground;
+ updateColorAndBackgroundVisibility();
+ }
+
+ /**
+ * Set the location of the lock icon.
+ */
+ @VisibleForTesting
public void setCenterLocation(@NonNull PointF center, int radius) {
mLockIconCenter = center;
mRadius = radius;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index a41997c..52ebf2f 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -49,6 +49,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -72,7 +73,8 @@
/**
* Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
*
- * This view will only be shown if the user has UDFPS or FaceAuth enrolled
+ * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock
+ * icon will show a set distance from the bottom of the device.
*/
@StatusBarComponent.StatusBarScope
public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
@@ -106,6 +108,7 @@
@NonNull private CharSequence mUnlockedLabel;
@NonNull private CharSequence mLockedLabel;
@Nullable private final Vibrator mVibrator;
+ @Nullable private final AuthRippleController mAuthRippleController;
private boolean mIsDozing;
private boolean mIsBouncerShowing;
@@ -149,7 +152,8 @@
@NonNull AccessibilityManager accessibilityManager,
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
- @Nullable Vibrator vibrator
+ @Nullable Vibrator vibrator,
+ @Nullable AuthRippleController authRippleController
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -162,6 +166,7 @@
mConfigurationController = configurationController;
mExecutor = executor;
mVibrator = vibrator;
+ mAuthRippleController = authRippleController;
final Context context = view.getContext();
mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
@@ -340,7 +345,7 @@
}
private void updateColors() {
- mView.updateColorAndBackgroundVisibility(mUdfpsSupported);
+ mView.updateColorAndBackgroundVisibility();
}
private void updateConfiguration() {
@@ -420,6 +425,8 @@
boolean wasUdfpsEnrolled = mUdfpsEnrolled;
mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+ mView.setUseBackground(mUdfpsSupported);
+
mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
updateVisibility();
@@ -621,6 +628,9 @@
// pre-emptively set to true to hide view
mIsBouncerShowing = true;
+ if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+ mAuthRippleController.showRipple(FINGERPRINT);
+ }
updateVisibility();
if (mOnGestureDetectedRunnable != null) {
mOnGestureDetectedRunnable.run();
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
index 3a0357d..4331f52 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
@@ -16,7 +16,8 @@
package com.android.keyguard.dagger;
-import com.android.systemui.statusbar.phone.UserAvatarView;
+import android.widget.FrameLayout;
+
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import dagger.BindsInstance;
@@ -31,8 +32,7 @@
/** Simple factory for {@link KeyguardUserSwitcherComponent}. */
@Subcomponent.Factory
interface Factory {
- KeyguardQsUserSwitchComponent build(
- @BindsInstance UserAvatarView userAvatarView);
+ KeyguardQsUserSwitchComponent build(@BindsInstance FrameLayout userAvatarContainer);
}
/** Builds a {@link KeyguardQsUserSwitchController}. */
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 76f30a8..00b33a4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -65,6 +65,7 @@
import com.android.systemui.power.PowerUI;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.screenrecord.RecordingController;
@@ -364,6 +365,7 @@
@Inject Lazy<UiEventLogger> mUiEventLogger;
@Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
@Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
+ @Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
@Inject
public Dependency() {
@@ -578,6 +580,7 @@
mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
mProviders.put(EdgeBackGestureHandler.Factory.class,
mEdgeBackGestureHandlerFactoryLazy::get);
+ mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get);
mProviders.put(UiEventLogger.class, mUiEventLogger::get);
mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index a68f796..8379ccf 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -20,6 +20,8 @@
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -29,7 +31,6 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
-import android.view.DisplayInfo;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -90,7 +91,7 @@
mMiniBitmap = null;
}
- class GLEngine extends Engine {
+ class GLEngine extends Engine implements DisplayListener {
// Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
// set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
@VisibleForTesting
@@ -102,15 +103,15 @@
private EglHelper mEglHelper;
private final Runnable mFinishRenderingTask = this::finishRendering;
private boolean mNeedRedraw;
- private int mWidth = 1;
- private int mHeight = 1;
+
+ private boolean mDisplaySizeValid = false;
+ private int mDisplayWidth = 1;
+ private int mDisplayHeight = 1;
+
private int mImgWidth = 1;
private int mImgHeight = 1;
- private float mPageWidth = 1.f;
- private float mPageOffset = 1.f;
- GLEngine() {
- }
+ GLEngine() { }
@VisibleForTesting
GLEngine(Handler handler) {
@@ -119,18 +120,29 @@
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
+ Trace.beginSection("ImageWallpaper.Engine#onCreate");
mEglHelper = getEglHelperInstance();
// Deferred init renderer because we need to get wallpaper by display context.
mRenderer = getRendererInstance();
setFixedSizeAllowed(true);
updateSurfaceSize();
- Rect window = getDisplayContext()
- .getSystemService(WindowManager.class)
- .getCurrentWindowMetrics()
- .getBounds();
- mHeight = window.height();
- mWidth = window.width();
mRenderer.setOnBitmapChanged(this::updateMiniBitmap);
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, mWorker.getThreadHandler());
+ Trace.endSection();
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) { }
+
+ @Override
+ public void onDisplayRemoved(int displayId) { }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == getDisplayContext().getDisplayId()) {
+ mDisplaySizeValid = false;
+ }
}
EglHelper getEglHelperInstance() {
@@ -154,26 +166,10 @@
if (pages == mPages) return;
mPages = pages;
if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
- updateShift();
mWorker.getThreadHandler().post(() ->
computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
}
- private void updateShift() {
- if (mImgHeight == 0) {
- mPageOffset = 0;
- mPageWidth = 1;
- return;
- }
- // calculate shift
- DisplayInfo displayInfo = new DisplayInfo();
- getDisplayContext().getDisplay().getDisplayInfo(displayInfo);
- int screenWidth = displayInfo.getNaturalWidth();
- float imgWidth = Math.min(mImgWidth > 0 ? screenWidth / (float) mImgWidth : 1.f, 1.f);
- mPageWidth = imgWidth;
- mPageOffset = (1 - imgWidth) / (float) (mPages - 1);
- }
-
private void updateMiniBitmap(Bitmap b) {
if (b == null) return;
int size = Math.min(b.getWidth(), b.getHeight());
@@ -203,13 +199,22 @@
}
@Override
+ public boolean shouldWaitForEngineShown() {
+ return true;
+ }
+
+ @Override
public void onDestroy() {
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(this);
mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
+ Trace.beginSection("ImageWallpaper.Engine#onDestroy");
mRenderer.finish();
mRenderer = null;
mEglHelper.finish();
mEglHelper = null;
+ Trace.endSection();
});
}
@@ -268,6 +273,16 @@
* (1-Wr)].
*/
private RectF pageToImgRect(RectF area) {
+ if (!mDisplaySizeValid) {
+ Rect window = getDisplayContext()
+ .getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getBounds();
+ mDisplayWidth = window.width();
+ mDisplayHeight = window.height();
+ mDisplaySizeValid = true;
+ }
+
// Width of a page for the caller of this API.
float virtualPageWidth = 1f / (float) mPages;
float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
@@ -275,12 +290,24 @@
int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
RectF imgArea = new RectF();
+
+ if (mImgWidth == 0 || mImgHeight == 0 || mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+ return imgArea;
+ }
+
imgArea.bottom = area.bottom;
imgArea.top = area.top;
+
+ float imageScale = Math.min(((float) mImgHeight) / mDisplayHeight, 1);
+ float mappedScreenWidth = mDisplayWidth * imageScale;
+ float pageWidth = Math.min(1.0f,
+ mImgWidth > 0 ? mappedScreenWidth / (float) mImgWidth : 1.f);
+ float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
imgArea.left = MathUtils.constrain(
- leftPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1);
+ leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
imgArea.right = MathUtils.constrain(
- rightPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1);
+ rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
if (imgArea.left > imgArea.right) {
// take full page
imgArea.left = 0;
@@ -293,7 +320,6 @@
private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
Bitmap b) {
List<WallpaperColors> colors = new ArrayList<>(areas.size());
- updateShift();
for (int i = 0; i < areas.size(); i++) {
RectF area = pageToImgRect(areas.get(i));
if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
@@ -322,8 +348,10 @@
public void onSurfaceCreated(SurfaceHolder holder) {
if (mWorker == null) return;
mWorker.getThreadHandler().post(() -> {
+ Trace.beginSection("ImageWallpaper#onSurfaceCreated");
mEglHelper.init(holder, needSupportWideColorGamut());
mRenderer.onSurfaceCreated();
+ Trace.endSection();
});
}
@@ -340,9 +368,11 @@
}
private void drawFrame() {
+ Trace.beginSection("ImageWallpaper#drawFrame");
preRender();
requestRender();
postRender();
+ Trace.endSection();
}
public void preRender() {
@@ -409,6 +439,7 @@
// This method should only be invoked from worker thread.
Trace.beginSection("ImageWallpaper#postRender");
scheduleFinishRendering();
+ reportEngineShown(false /* waitForEngineShown */);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index affad7a..8c63f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -438,6 +438,12 @@
private boolean mCancelled;
@Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mCallback.onBeginDrag(animView);
+ }
+
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
index 81a13a2..4082015 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
@@ -57,7 +57,9 @@
public void run() {
mView.removeCallbacks(this);
mView.show(false /* show */, true /* animate */, () -> {
- mWindowManager.removeView(mView);
+ if (mView.isAttachedToWindow()) {
+ mWindowManager.removeView(mView);
+ }
});
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index c9e6771..5616a00 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -40,10 +40,10 @@
* After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
* indicating that restoring is finished for a given user.
*/
-class BackupHelper : BackupAgentHelper() {
+open class BackupHelper : BackupAgentHelper() {
companion object {
- private const val TAG = "BackupHelper"
+ const val TAG = "BackupHelper"
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 3f61d3c..fd37b35 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -126,6 +126,7 @@
boolean mCredentialAllowed;
boolean mSkipIntro;
long mOperationId;
+ long mRequestId;
@BiometricMultiSensorMode int mMultiSensorConfig;
}
@@ -172,6 +173,12 @@
return this;
}
+ /** Unique id for this request. */
+ public Builder setRequestId(long requestId) {
+ mConfig.mRequestId = requestId;
+ return this;
+ }
+
/** The multi-sensor mode. */
public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
mConfig.mMultiSensorConfig = multiSensorConfig;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a4123c7..0790af9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -42,6 +42,7 @@
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
@@ -49,6 +50,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.WindowManager;
@@ -76,6 +78,9 @@
/**
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
* appropriate biometric UI (e.g. BiometricDialogView).
+ *
+ * Also coordinates biometric-related things, such as UDFPS, with
+ * {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@@ -115,6 +120,8 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
+ @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+
private class BiometricTaskStackListener extends TaskStackListener {
@Override
public void onTaskStackChanged() {
@@ -122,6 +129,21 @@
}
}
+ private final FingerprintStateListener mFingerprintStateListener =
+ new FingerprintStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ Log.d(TAG, "onEnrollmentsChanged, userId: " + userId
+ + ", sensorId: " + sensorId
+ + ", hasEnrollments: " + hasEnrollments);
+ for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) {
+ if (prop.sensorId == sensorId) {
+ mUdfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
+ };
+
@NonNull
private final IFingerprintAuthenticatorsRegisteredCallback
mFingerprintAuthenticatorsRegisteredCallback =
@@ -436,6 +458,7 @@
mUdfpsControllerFactory = udfpsControllerFactory;
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
+ mUdfpsEnrolledForUser = new SparseBooleanArray();
mOrientationListener = new BiometricOrientationEventListener(context,
() -> {
onOrientationChanged();
@@ -474,6 +497,7 @@
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
mFingerprintAuthenticatorsRegisteredCallback);
+ mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener);
}
mTaskStackListener = new BiometricTaskStackListener();
@@ -501,7 +525,7 @@
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
@Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
@@ -515,6 +539,7 @@
+ ", credentialAllowed: " + credentialAllowed
+ ", requireConfirmation: " + requireConfirmation
+ ", operationId: " + operationId
+ + ", requestId: " + requestId
+ ", multiSensorConfig: " + multiSensorConfig);
}
SomeArgs args = SomeArgs.obtain();
@@ -526,6 +551,7 @@
args.argi1 = userId;
args.arg6 = opPackageName;
args.arg7 = operationId;
+ args.arg8 = requestId;
args.argi2 = multiSensorConfig;
boolean skipAnimation = false;
@@ -629,6 +655,7 @@
if (mCurrentDialog == null) {
// Could be possible if the caller canceled authentication after credential success
// but before the client was notified.
+ if (DEBUG) Log.d(TAG, "dialog already gone");
return;
}
@@ -670,7 +697,7 @@
return false;
}
- return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps);
+ return mUdfpsEnrolledForUser.get(userId);
}
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
@@ -683,6 +710,7 @@
final int userId = args.argi1;
final String opPackageName = (String) args.arg6;
final long operationId = (long) args.arg7;
+ final long requestId = (long) args.arg8;
final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
// Create a new dialog but do not replace the current one yet.
@@ -695,6 +723,7 @@
opPackageName,
skipAnimation,
operationId,
+ requestId,
multiSensorConfig);
if (newDialog == null) {
@@ -772,7 +801,7 @@
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
- boolean skipIntro, long operationId,
+ boolean skipIntro, long operationId, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
@@ -782,10 +811,15 @@
.setOpPackageName(opPackageName)
.setSkipIntro(skipIntro)
.setOperationId(operationId)
+ .setRequestId(requestId)
.setMultiSensorConfig(multiSensorConfig)
.build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
}
+ /**
+ * AuthController callback used to receive signal for when biometric authenticators are
+ * registered.
+ */
public interface Callback {
/**
* Called when authenticators are registered. If authenticators are already
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 54f9321..420ae53 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -39,11 +39,9 @@
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.media.AudioAttributes;
import android.os.Handler;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -64,7 +62,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -72,10 +69,12 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import com.android.systemui.util.time.SystemClock;
import java.util.HashSet;
import java.util.Optional;
@@ -93,7 +92,7 @@
* controls/manages all UDFPS sensors. In other words, a single controller is registered with
* {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such
* as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or
- * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have
+ * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have
* {@code sensorId} parameters.
*/
@SuppressWarnings("deprecation")
@@ -117,9 +116,7 @@
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final DumpManager mDumpManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@Nullable private final Vibrator mVibrator;
- @NonNull private final Handler mMainHandler;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -127,6 +124,9 @@
@Nullable private final UdfpsHbmProvider mHbmProvider;
@NonNull private final KeyguardBypassController mKeyguardBypassController;
@NonNull private final ConfigurationController mConfigurationController;
+ @NonNull private final SystemClock mSystemClock;
+ @NonNull private final UnlockedScreenOffAnimationController
+ mUnlockedScreenOffAnimationController;
@VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -165,7 +165,8 @@
public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ // vibration will bypass battery saver mode:
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
.build();
public static final VibrationEffect EFFECT_CLICK =
@@ -213,14 +214,12 @@
}
void onAcquiredGood() {
- Log.d(TAG, "onAcquiredGood");
if (mEnrollHelper != null) {
mEnrollHelper.animateIfLastStep();
}
}
void onEnrollmentHelp() {
- Log.d(TAG, "onEnrollmentHelp");
if (mEnrollHelper != null) {
mEnrollHelper.onEnrollmentHelp();
}
@@ -243,7 +242,7 @@
final UdfpsEnrollHelper enrollHelper;
if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
|| reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
- enrollHelper = new UdfpsEnrollHelper(mContext, reason);
+ enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason);
} else {
enrollHelper = null;
}
@@ -454,19 +453,19 @@
final String touchInfo = String.format(
"minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
minor, major, v, exceedsVelocityThreshold);
- final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime;
+ final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
if (!isIlluminationRequested && !mGoodCaptureReceived &&
!exceedsVelocityThreshold) {
onFingerDown((int) event.getRawX(), (int) event.getRawY(), minor,
major);
Log.v(TAG, "onTouch | finger down: " + touchInfo);
- mTouchLogTime = SystemClock.elapsedRealtime();
- mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ mTouchLogTime = mSystemClock.elapsedRealtime();
+ mPowerManager.userActivity(mSystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
handled = true;
} else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
Log.v(TAG, "onTouch | finger move: " + touchInfo);
- mTouchLogTime = SystemClock.elapsedRealtime();
+ mTouchLogTime = mSystemClock.elapsedRealtime();
}
} else {
Log.v(TAG, "onTouch | finger outside");
@@ -517,7 +516,6 @@
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -530,11 +528,12 @@
@NonNull KeyguardBypassController keyguardBypassController,
@NonNull DisplayManager displayManager,
@Main Handler mainHandler,
- @NonNull ConfigurationController configurationController) {
+ @NonNull ConfigurationController configurationController,
+ @NonNull SystemClock systemClock,
+ @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mContext = context;
mExecution = execution;
// TODO (b/185124905): inject main handler and vibrator once done prototyping
- mMainHandler = new Handler(Looper.getMainLooper());
mVibrator = vibrator;
mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -548,7 +547,6 @@
mKeyguardViewManager = statusBarKeyguardViewManager;
mDumpManager = dumpManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardViewMediator = keyguardViewMediator;
mFalsingManager = falsingManager;
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
@@ -566,6 +564,8 @@
mainHandler);
mKeyguardBypassController = keyguardBypassController;
mConfigurationController = configurationController;
+ mSystemClock = systemClock;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -655,6 +655,20 @@
}
}
+ private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) {
+ if (!(animation instanceof UdfpsKeyguardViewController)) {
+ // always rotate view if we're not on the keyguard
+ return true;
+ }
+
+ // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded
+ if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) {
+ return false;
+ }
+
+ return true;
+ }
+
private WindowManager.LayoutParams computeLayoutParams(
@Nullable UdfpsAnimationViewController animation) {
final int paddingX = animation != null ? animation.getPaddingX() : 0;
@@ -678,9 +692,11 @@
// Transform dimensions if the device is in landscape mode
switch (mContext.getDisplay().getRotation()) {
case Surface.ROTATION_90:
- if (animation instanceof UdfpsKeyguardViewController
- && mKeyguardUpdateMonitor.isGoingToSleep()) {
+ if (!shouldRotate(animation)) {
+ Log.v(TAG, "skip rotating udfps location ROTATION_90");
break;
+ } else {
+ Log.v(TAG, "rotate udfps location ROTATION_90");
}
mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius
- paddingX;
@@ -689,9 +705,11 @@
break;
case Surface.ROTATION_270:
- if (animation instanceof UdfpsKeyguardViewController
- && mKeyguardUpdateMonitor.isGoingToSleep()) {
+ if (!shouldRotate(animation)) {
+ Log.v(TAG, "skip rotating udfps location ROTATION_270");
break;
+ } else {
+ Log.v(TAG, "rotate udfps location ROTATION_270");
}
mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius
- paddingX;
@@ -708,15 +726,21 @@
return mCoreLayoutParams;
}
+
private void onOrientationChanged() {
// When the configuration changes it's almost always necessary to destroy and re-create
// the overlay's window to pass it the new LayoutParams.
// Hiding the overlay will destroy its window. It's safe to hide the overlay regardless
// of whether it is already hidden.
+ final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
hideUdfpsOverlay();
+
// If the overlay needs to be shown, this will re-create and show the overlay with the
// updated LayoutParams. Otherwise, the overlay will remain hidden.
updateOverlay();
+ if (wasShowingAltAuth) {
+ mKeyguardViewManager.showGenericBouncer(true);
+ }
}
private void showUdfpsOverlay(@NonNull ServerRequest request) {
@@ -782,12 +806,12 @@
mStatusBar,
mKeyguardViewManager,
mKeyguardUpdateMonitor,
- mFgExecutor,
mDumpManager,
- mKeyguardViewMediator,
mLockscreenShadeTransitionController,
mConfigurationController,
+ mSystemClock,
mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
this
);
case IUdfpsOverlayController.REASON_AUTH_BP:
@@ -823,10 +847,14 @@
Log.v(TAG, "hideUdfpsOverlay | removing window");
// Reset the controller back to its starting state.
onFingerUp();
+ boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
mWindowManager.removeView(mView);
mView.setOnTouchListener(null);
mView.setOnHoverListener(null);
mView.setAnimationViewController(null);
+ if (wasShowingAltAuth) {
+ mKeyguardViewManager.resetAlternateAuth(true);
+ }
mAccessibilityManager.removeTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
mView = null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 8ac6df7..caec1d5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PointF;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Build;
import android.os.UserHandle;
@@ -44,16 +45,6 @@
private static final String NEW_COORDS_OVERRIDE =
"com.android.systemui.biometrics.UdfpsNewCoords";
- static final int ENROLL_STAGE_COUNT = 4;
-
- // TODO(b/198928407): Consolidate with FingerprintEnrollEnrolling
- private static final int[] STAGE_THRESHOLDS = new int[] {
- 2, // center
- 18, // guided
- 22, // fingertip
- 38, // edges
- };
-
interface Listener {
void onEnrollmentProgress(int remaining, int totalSteps);
void onEnrollmentHelp(int remaining, int totalSteps);
@@ -61,6 +52,7 @@
}
@NonNull private final Context mContext;
+ @NonNull private final FingerprintManager mFingerprintManager;
// IUdfpsOverlayController reason
private final int mEnrollReason;
private final boolean mAccessibilityEnabled;
@@ -77,8 +69,11 @@
@Nullable Listener mListener;
- public UdfpsEnrollHelper(@NonNull Context context, int reason) {
+ public UdfpsEnrollHelper(@NonNull Context context,
+ @NonNull FingerprintManager fingerprintManager, int reason) {
+
mContext = context;
+ mFingerprintManager = fingerprintManager;
mEnrollReason = reason;
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
@@ -127,12 +122,12 @@
}
}
- static int getStageThreshold(int index) {
- return STAGE_THRESHOLDS[index];
+ int getStageCount() {
+ return mFingerprintManager.getEnrollStageCount();
}
- static int getLastStageThreshold() {
- return STAGE_THRESHOLDS[ENROLL_STAGE_COUNT - 1];
+ int getStageThresholdSteps(int totalSteps, int stageIndex) {
+ return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
}
boolean shouldShowProgressBar() {
@@ -140,11 +135,9 @@
}
void onEnrollmentProgress(int remaining) {
- Log.d(TAG, "onEnrollmentProgress: remaining = " + remaining
- + ", mRemainingSteps = " + mRemainingSteps
- + ", mTotalSteps = " + mTotalSteps
- + ", mLocationsEnrolled = " + mLocationsEnrolled
- + ", mCenterTouchCount = " + mCenterTouchCount);
+ if (mTotalSteps == -1) {
+ mTotalSteps = remaining;
+ }
if (remaining != mRemainingSteps) {
mLocationsEnrolled++;
@@ -153,19 +146,6 @@
}
}
- if (mTotalSteps == -1) {
- mTotalSteps = remaining;
-
- // Allocate (or subtract) any extra steps for the first enroll stage.
- final int extraSteps = mTotalSteps - getLastStageThreshold();
- if (extraSteps != 0) {
- for (int stageIndex = 0; stageIndex < ENROLL_STAGE_COUNT; stageIndex++) {
- STAGE_THRESHOLDS[stageIndex] =
- Math.max(0, STAGE_THRESHOLDS[stageIndex] + extraSteps);
- }
- }
- }
-
mRemainingSteps = remaining;
if (mListener != null) {
@@ -194,7 +174,7 @@
if (mTotalSteps == -1 || mRemainingSteps == -1) {
return true;
}
- return mTotalSteps - mRemainingSteps < STAGE_THRESHOLDS[0];
+ return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
}
boolean isGuidedEnrollmentStage() {
@@ -202,7 +182,8 @@
return false;
}
final int progressSteps = mTotalSteps - mRemainingSteps;
- return progressSteps >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1];
+ return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
+ && progressSteps < getStageThresholdSteps(mTotalSteps, 1);
}
boolean isTipEnrollmentStage() {
@@ -210,14 +191,15 @@
return false;
}
final int progressSteps = mTotalSteps - mRemainingSteps;
- return progressSteps >= STAGE_THRESHOLDS[1] && progressSteps < STAGE_THRESHOLDS[2];
+ return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
+ && progressSteps < getStageThresholdSteps(mTotalSteps, 2);
}
boolean isEdgeEnrollmentStage() {
if (mTotalSteps == -1 || mRemainingSteps == -1) {
return false;
}
- return mTotalSteps - mRemainingSteps >= STAGE_THRESHOLDS[2];
+ return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
}
@NonNull
@@ -239,7 +221,6 @@
}
void animateIfLastStep() {
- Log.d(TAG, "animateIfLastStep: mRemainingSteps = " + mRemainingSteps);
if (mListener == null) {
Log.e(TAG, "animateIfLastStep, null listener");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index b56543f..b2a5409 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -36,78 +36,113 @@
private static final float SEGMENT_GAP_ANGLE = 12f;
- @NonNull private final List<UdfpsEnrollProgressBarSegment> mSegments;
+ @NonNull private final Context mContext;
+
+ @Nullable private UdfpsEnrollHelper mEnrollHelper;
+ @NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
+ private int mTotalSteps = 1;
+ private int mProgressSteps = 0;
+ private boolean mIsShowingHelp = false;
public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
- mSegments = new ArrayList<>(UdfpsEnrollHelper.ENROLL_STAGE_COUNT);
- float startAngle = SEGMENT_GAP_ANGLE / 2f;
- final float sweepAngle = (360f / UdfpsEnrollHelper.ENROLL_STAGE_COUNT) - SEGMENT_GAP_ANGLE;
- final Runnable invalidateRunnable = this::invalidateSelf;
- for (int index = 0; index < UdfpsEnrollHelper.ENROLL_STAGE_COUNT; index++) {
- mSegments.add(new UdfpsEnrollProgressBarSegment(context, getBounds(), startAngle,
- sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
- startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
+ mContext = context;
+ }
+
+ void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) {
+ mEnrollHelper = enrollHelper;
+ if (enrollHelper != null) {
+ final int stageCount = enrollHelper.getStageCount();
+ mSegments = new ArrayList<>(stageCount);
+ float startAngle = SEGMENT_GAP_ANGLE / 2f;
+ final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE;
+ final Runnable invalidateRunnable = this::invalidateSelf;
+ for (int index = 0; index < stageCount; index++) {
+ mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle,
+ sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
+ startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
+ }
+ invalidateSelf();
}
}
- void setEnrollmentProgress(int remaining, int totalSteps) {
- if (remaining == totalSteps) {
- // Show some progress for the initial touch.
- setEnrollmentProgress(1);
- } else {
- setEnrollmentProgress(totalSteps - remaining);
- }
+ void onEnrollmentProgress(int remaining, int totalSteps) {
+ mTotalSteps = totalSteps;
+ updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */);
}
- private void setEnrollmentProgress(int progressSteps) {
- Log.d(TAG, "setEnrollmentProgress: progressSteps = " + progressSteps);
+ void onEnrollmentHelp(int remaining, int totalSteps) {
+ updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */);
+ }
- int segmentIndex = 0;
+ void onLastStepAcquired() {
+ updateState(mTotalSteps, false /* isShowingHelp */);
+ }
+
+ private static int getProgressSteps(int remaining, int totalSteps) {
+ // Show some progress for the initial touch.
+ return Math.max(1, totalSteps - remaining);
+ }
+
+ private void updateState(int progressSteps, boolean isShowingHelp) {
+ updateProgress(progressSteps);
+ updateFillColor(isShowingHelp);
+ }
+
+ private void updateProgress(int progressSteps) {
+ if (mProgressSteps == progressSteps) {
+ return;
+ }
+ mProgressSteps = progressSteps;
+
+ if (mEnrollHelper == null) {
+ Log.e(TAG, "updateState: UDFPS enroll helper was null");
+ return;
+ }
+
+ int index = 0;
int prevThreshold = 0;
- while (segmentIndex < mSegments.size()) {
- final UdfpsEnrollProgressBarSegment segment = mSegments.get(segmentIndex);
- final int threshold = UdfpsEnrollHelper.getStageThreshold(segmentIndex);
-
- if (progressSteps >= threshold && !segment.isFilledOrFilling()) {
- Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] complete");
+ while (index < mSegments.size()) {
+ final UdfpsEnrollProgressBarSegment segment = mSegments.get(index);
+ final int thresholdSteps = mEnrollHelper.getStageThresholdSteps(mTotalSteps, index);
+ if (progressSteps >= thresholdSteps && segment.getProgress() < 1f) {
segment.updateProgress(1f);
break;
- } else if (progressSteps >= prevThreshold && progressSteps < threshold) {
+ } else if (progressSteps >= prevThreshold && progressSteps < thresholdSteps) {
final int relativeSteps = progressSteps - prevThreshold;
- final int relativeThreshold = threshold - prevThreshold;
+ final int relativeThreshold = thresholdSteps - prevThreshold;
final float segmentProgress = (float) relativeSteps / (float) relativeThreshold;
- Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] progress = "
- + segmentProgress);
segment.updateProgress(segmentProgress);
break;
}
- segmentIndex++;
- prevThreshold = threshold;
+ index++;
+ prevThreshold = thresholdSteps;
}
- if (progressSteps >= UdfpsEnrollHelper.getLastStageThreshold()) {
- Log.d(TAG, "setEnrollmentProgress: startCompletionAnimation");
+ if (progressSteps >= mTotalSteps) {
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
segment.startCompletionAnimation();
}
} else {
- Log.d(TAG, "setEnrollmentProgress: cancelCompletionAnimation");
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
segment.cancelCompletionAnimation();
}
}
}
- void onLastStepAcquired() {
- Log.d(TAG, "setEnrollmentProgress: onLastStepAcquired");
- setEnrollmentProgress(UdfpsEnrollHelper.getLastStageThreshold());
+ private void updateFillColor(boolean isShowingHelp) {
+ if (mIsShowingHelp == isShowingHelp) {
+ return;
+ }
+ mIsShowingHelp = isShowingHelp;
+
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.updateFillColor(isShowingHelp);
+ }
}
@Override
public void draw(@NonNull Canvas canvas) {
- Log.d(TAG, "setEnrollmentProgress: draw");
-
canvas.save();
// Progress starts from the top, instead of the right
@@ -123,12 +158,10 @@
@Override
public void setAlpha(int alpha) {
-
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
-
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
index 5f24380..bd6ab44 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
@@ -39,6 +39,7 @@
public class UdfpsEnrollProgressBarSegment {
private static final String TAG = "UdfpsProgressBarSegment";
+ private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L;
private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L;
@@ -53,16 +54,21 @@
private final float mSweepAngle;
private final float mMaxOverSweepAngle;
private final float mStrokeWidthPx;
+ @ColorInt private final int mProgressColor;
+ @ColorInt private final int mHelpColor;
@NonNull private final Paint mBackgroundPaint;
@NonNull private final Paint mProgressPaint;
- private boolean mIsFilledOrFilling = false;
-
private float mProgress = 0f;
+ private float mAnimatedProgress = 0f;
@Nullable private ValueAnimator mProgressAnimator;
@NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
+ private boolean mIsShowingHelp = false;
+ @Nullable private ValueAnimator mFillColorAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
+
private float mOverSweepAngle = 0f;
@Nullable private ValueAnimator mOverSweepAnimator;
@Nullable private ValueAnimator mOverSweepReverseAnimator;
@@ -79,6 +85,8 @@
mSweepAngle = sweepAngle;
mMaxOverSweepAngle = maxOverSweepAngle;
mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
+ mProgressColor = context.getColor(R.color.udfps_enroll_progress);
+ mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
mBackgroundPaint = new Paint();
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
@@ -100,13 +108,18 @@
// Progress should not be color extracted
mProgressPaint = new Paint();
mProgressPaint.setStrokeWidth(mStrokeWidthPx);
- mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress));
+ mProgressPaint.setColor(mProgressColor);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
mProgressUpdateListener = animation -> {
- mProgress = (float) animation.getAnimatedValue();
+ mAnimatedProgress = (float) animation.getAnimatedValue();
+ mInvalidateRunnable.run();
+ };
+
+ mFillColorUpdateListener = animation -> {
+ mProgressPaint.setColor((int) animation.getAnimatedValue());
mInvalidateRunnable.run();
};
@@ -129,11 +142,9 @@
* Draws this segment to the given canvas.
*/
public void draw(@NonNull Canvas canvas) {
- Log.d(TAG, "draw: mProgress = " + mProgress);
-
final float halfPaddingPx = mStrokeWidthPx / 2f;
- if (mProgress < 1f) {
+ if (mAnimatedProgress < 1f) {
// Draw the unfilled background color of the segment.
canvas.drawArc(
halfPaddingPx,
@@ -146,7 +157,7 @@
mBackgroundPaint);
}
- if (mProgress > 0f) {
+ if (mAnimatedProgress > 0f) {
// Draw the filled progress portion of the segment.
canvas.drawArc(
halfPaddingPx,
@@ -154,17 +165,18 @@
mBounds.right - halfPaddingPx,
mBounds.bottom - halfPaddingPx,
mStartAngle,
- mSweepAngle * mProgress + mOverSweepAngle,
+ mSweepAngle * mAnimatedProgress + mOverSweepAngle,
false /* useCenter */,
mProgressPaint);
}
}
/**
- * @return Whether this segment is filled or in the process of being filled.
+ * @return The fill progress of this segment, in the range [0, 1]. If fill progress is being
+ * animated, returns the value it is animating to.
*/
- public boolean isFilledOrFilling() {
- return mIsFilledOrFilling;
+ public float getProgress() {
+ return mProgress;
}
/**
@@ -177,27 +189,44 @@
}
private void updateProgress(float progress, long animationDurationMs) {
- Log.d(TAG, "updateProgress: progress = " + progress
- + ", duration = " + animationDurationMs);
-
if (mProgress == progress) {
- Log.d(TAG, "updateProgress skipped: progress == mProgress");
return;
}
-
- mIsFilledOrFilling = progress >= 1f;
+ mProgress = progress;
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
mProgressAnimator.cancel();
}
- mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
+ mProgressAnimator = ValueAnimator.ofFloat(mAnimatedProgress, progress);
mProgressAnimator.setDuration(animationDurationMs);
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
mProgressAnimator.start();
}
/**
+ * Updates the fill color of this segment, animating if necessary.
+ *
+ * @param isShowingHelp Whether fill color should indicate that a help message is being shown.
+ */
+ public void updateFillColor(boolean isShowingHelp) {
+ if (mIsShowingHelp == isShowingHelp) {
+ return;
+ }
+ mIsShowingHelp = isShowingHelp;
+
+ if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
+ mFillColorAnimator.cancel();
+ }
+
+ @ColorInt final int targetColor = isShowingHelp ? mHelpColor : mProgressColor;
+ mFillColorAnimator = ValueAnimator.ofArgb(mProgressPaint.getColor(), targetColor);
+ mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+ mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
+ mFillColorAnimator.start();
+ }
+
+ /**
* Queues and runs the completion animation for this segment.
*/
public void startCompletionAnimation() {
@@ -208,18 +237,16 @@
return;
}
- Log.d(TAG, "startCompletionAnimation: mProgress = " + mProgress
- + ", mOverSweepAngle = " + mOverSweepAngle);
-
// Reset sweep angle back to zero if the animation is being rolled back.
if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
mOverSweepReverseAnimator.cancel();
mOverSweepAngle = 0f;
}
- // Start filling the segment if it isn't already.
- if (mProgress < 1f) {
+ // Clear help color and start filling the segment if it isn't already.
+ if (mAnimatedProgress < 1f) {
updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS);
+ updateFillColor(false /* isShowingHelp */);
}
// Queue the animation to run after fill completes.
@@ -230,9 +257,6 @@
* Cancels (and reverses, if necessary) a queued or running completion animation.
*/
public void cancelCompletionAnimation() {
- Log.d(TAG, "cancelCompletionAnimation: mProgress = " + mProgress
- + ", mOverSweepAngle = " + mOverSweepAngle);
-
// Cancel the animation if it's queued or running.
mHandler.removeCallbacks(mOverSweepAnimationRunnable);
if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 6f02c64..64b0968 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -73,24 +73,22 @@
}
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
+ mFingerprintProgressDrawable.setEnrollHelper(enrollHelper);
mFingerprintDrawable.setEnrollHelper(enrollHelper);
}
void onEnrollmentProgress(int remaining, int totalSteps) {
mHandler.post(() -> {
- mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps);
+ mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
});
}
void onEnrollmentHelp(int remaining, int totalSteps) {
- mHandler.post(
- () -> mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps));
+ mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
}
void onLastStepAcquired() {
- mHandler.post(() -> {
- mFingerprintProgressDrawable.onLastStepAcquired();
- });
+ mHandler.post(mFingerprintProgressDrawable::onLastStepAcquired);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 8886723..c128e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -26,16 +26,16 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -46,12 +46,13 @@
public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final DelayableExecutor mExecutor;
- @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
@NonNull private final ConfigurationController mConfigurationController;
+ @NonNull private final SystemClock mSystemClock;
@NonNull private final KeyguardStateController mKeyguardStateController;
@NonNull private final UdfpsController mUdfpsController;
+ @NonNull private final UnlockedScreenOffAnimationController
+ mUnlockedScreenOffAnimationController;
private boolean mShowingUdfpsBouncer;
private boolean mUdfpsRequested;
@@ -60,7 +61,7 @@
private int mStatusBarState;
private float mTransitionToFullShadeProgress;
private float mLastDozeAmount;
-
+ private long mLastUdfpsBouncerShowTime = -1;
private float mStatusBarExpansion;
private boolean mLaunchTransitionFadingAway;
@@ -78,22 +79,22 @@
@NonNull StatusBar statusBar,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull DelayableExecutor mainDelayableExecutor,
@NonNull DumpManager dumpManager,
- @NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull LockscreenShadeTransitionController transitionController,
@NonNull ConfigurationController configurationController,
+ @NonNull SystemClock systemClock,
@NonNull KeyguardStateController keyguardStateController,
+ @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@NonNull UdfpsController udfpsController) {
super(view, statusBarStateController, statusBar, dumpManager);
mKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mExecutor = mainDelayableExecutor;
- mKeyguardViewMediator = keyguardViewMediator;
mLockScreenShadeTransitionController = transitionController;
mConfigurationController = configurationController;
+ mSystemClock = systemClock;
mKeyguardStateController = keyguardStateController;
mUdfpsController = udfpsController;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
}
@Override
@@ -102,6 +103,12 @@
}
@Override
+ public void onInit() {
+ super.onInit();
+ mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+ }
+
+ @Override
protected void onViewAttached() {
super.onViewAttached();
final float dozeAmount = mStatusBarStateController.getDozeAmount();
@@ -124,6 +131,7 @@
mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
+ mUnlockedScreenOffAnimationController.addCallback(mUnlockedScreenOffCallback);
}
@Override
@@ -140,6 +148,7 @@
if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
}
+ mUnlockedScreenOffAnimationController.removeCallback(mUnlockedScreenOffCallback);
}
@Override
@@ -170,6 +179,9 @@
boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
mShowingUdfpsBouncer = show;
if (mShowingUdfpsBouncer) {
+ mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
+ }
+ if (mShowingUdfpsBouncer) {
if (udfpsAffordanceWasNotShowing) {
mView.animateInUdfpsBouncer(null);
}
@@ -237,16 +249,25 @@
* If we were previously showing the udfps bouncer, hide it and instead show the regular
* (pin/pattern/password) bouncer.
*
- * Does nothing if we weren't previously showing the udfps bouncer.
+ * Does nothing if we weren't previously showing the UDFPS bouncer.
*/
private void maybeShowInputBouncer() {
- if (mShowingUdfpsBouncer) {
+ if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
mKeyguardViewManager.showBouncer(true);
mKeyguardViewManager.resetAlternateAuth(false);
}
}
/**
+ * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
+ * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
+ * bouncer.
+ */
+ private boolean hasUdfpsBouncerShownWithMinTime() {
+ return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
+ }
+
+ /**
* Set the progress we're currently transitioning to the full shade. 0.0f means we're not
* transitioning yet, while 1.0f means we've fully dragged down.
*/
@@ -400,4 +421,7 @@
updatePauseAuth();
}
};
+
+ private final UnlockedScreenOffAnimationController.Callback mUnlockedScreenOffCallback =
+ (linear, eased) -> mStateListener.onDozeAmountChanged(linear, eased);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 4104e31..46a03e8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -16,6 +16,10 @@
package com.android.systemui.controls.ui
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
@@ -23,18 +27,25 @@
import android.view.WindowInsets.Type
import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
/**
- * Displays Device Controls inside an activity
+ * Displays Device Controls inside an activity. This activity is available to be displayed over the
+ * lockscreen if the user has allowed it via
+ * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be
+ * destroyed on SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as
+ * user expectations for the activity to not continue running.
*/
class ControlsActivity @Inject constructor(
- private val uiController: ControlsUiController
+ private val uiController: ControlsUiController,
+ private val broadcastDispatcher: BroadcastDispatcher
) : LifecycleActivity() {
private lateinit var parent: ViewGroup
+ private lateinit var broadcastReceiver: BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -62,6 +73,8 @@
WindowInsets.CONSUMED
}
}
+
+ initBroadcastReceiver()
}
override fun onResume() {
@@ -83,4 +96,25 @@
uiController.hide()
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+ }
+
+ private fun initBroadcastReceiver() {
+ broadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.getAction()
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ finish()
+ }
+ }
+ }
+
+ val filter = IntentFilter()
+ filter.addAction(Intent.ACTION_SCREEN_OFF)
+ broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 557bca8..5a52fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -256,13 +256,13 @@
// Force animations when transitioning from a dialog to an activity
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
- if (keyguardStateController.isUnlocked()) {
+ if (keyguardStateController.isShowing()) {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
+ } else {
activityContext.startActivity(
intent,
ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle()
)
- } else {
- activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index c97a30e..79ee6a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -198,9 +198,11 @@
@SysUISingleton
@Provides
static ThemeOverlayApplier provideThemeOverlayManager(Context context,
- @Background Executor bgExecutor, OverlayManager overlayManager,
+ @Background Executor bgExecutor,
+ @Main Executor mainExecutor,
+ OverlayManager overlayManager,
DumpManager dumpManager) {
- return new ThemeOverlayApplier(overlayManager, bgExecutor,
+ return new ThemeOverlayApplier(overlayManager, bgExecutor, mainExecutor,
context.getString(R.string.launcher_overlayable_package),
context.getString(R.string.themepicker_overlayable_package), dumpManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 657d924..845d7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -275,6 +275,13 @@
}
/**
+ * Appends sensor event dropped event to logs
+ */
+ public void traceSensorEventDropped(int sensorEvent, String reason) {
+ mLogger.logSensorEventDropped(sensorEvent, reason);
+ }
+
+ /**
* Appends pulse dropped event to logs
* @param reason why the pulse was dropped
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index fe37d49..dc18618 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -205,6 +205,15 @@
})
}
+ fun logSensorEventDropped(sensorEvent: Int, reason: String) {
+ buffer.log(TAG, INFO, {
+ int1 = sensorEvent
+ str1 = reason
+ }, {
+ "SensorEvent [$int1] dropped, reason=$str1"
+ })
+ }
+
fun logPulseDropped(reason: String) {
buffer.log(TAG, INFO, {
str1 = reason
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 8d4ac75..058f37a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -29,8 +29,8 @@
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
-import android.view.Display;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.dagger.BrightnessSensor;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
@@ -74,6 +74,7 @@
private final Optional<Sensor> mLightSensorOptional;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final DozeParameters mDozeParameters;
+ private final DockManager mDockManager;
private final int[] mSensorToBrightness;
private final int[] mSensorToScrimOpacity;
private final int mScreenBrightnessDim;
@@ -101,6 +102,7 @@
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
WakefulnessLifecycle wakefulnessLifecycle,
DozeParameters dozeParameters,
+ DockManager dockManager,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mContext = context;
mDozeService = service;
@@ -110,6 +112,7 @@
mDozeParameters = dozeParameters;
mDozeHost = host;
mHandler = handler;
+ mDockManager = dockManager;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
@@ -122,7 +125,15 @@
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
case INITIALIZED:
+ resetBrightnessToDefault();
+ break;
+ case DOZE_AOD:
+ case DOZE_REQUEST_PULSE:
+ case DOZE_AOD_DOCKED:
+ setLightSensorEnabled(true);
+ break;
case DOZE:
+ setLightSensorEnabled(false);
resetBrightnessToDefault();
break;
case FINISH:
@@ -135,15 +146,6 @@
}
}
- @Override
- public void onScreenState(int state) {
- if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
- setLightSensorEnabled(true);
- } else {
- setLightSensorEnabled(false);
- }
- }
-
private void onDestroy() {
setLightSensorEnabled(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index b2db86f..55e6154 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -456,13 +456,24 @@
public void updateListening() {
if (!mConfigured || mSensor == null) return;
- if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
- && !mRegistered) {
- mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
- if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
+ if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
+ if (!mRegistered) {
+ mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
+ if (DEBUG) {
+ Log.d(TAG, "requestTriggerSensor[" + mSensor
+ + "] " + mRegistered);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "requestTriggerSensor[" + mSensor
+ + "] already registered");
+ }
+ }
} else if (mRegistered) {
final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
- if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
+ if (DEBUG) {
+ Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt);
+ }
mRegistered = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 455f3c0..756adca 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -41,6 +41,7 @@
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -92,6 +93,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor;
+ private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
private long mNotificationPulseTime;
@@ -179,7 +181,8 @@
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings, AuthController authController,
@Main DelayableExecutor mainExecutor,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ KeyguardStateController keyguardStateController) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -198,6 +201,7 @@
mAuthController = authController;
mMainExecutor = mainExecutor;
mUiEventLogger = uiEventLogger;
+ mKeyguardStateController = keyguardStateController;
}
@Override
@@ -294,6 +298,7 @@
proximityCheckThenCall((result) -> {
if (result != null && result) {
// In pocket, drop event.
+ mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near");
return;
}
if (isDoubleTap || isTap) {
@@ -302,6 +307,10 @@
}
gentleWakeUp(pulseReason);
} else if (isPickup) {
+ if (shouldDropPickupEvent()) {
+ mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded");
+ return;
+ }
gentleWakeUp(pulseReason);
} else if (isUdfpsLongPress) {
final State state = mMachine.getState();
@@ -320,7 +329,7 @@
}, true /* alreadyPerformedProxCheck */, pulseReason);
}
- if (isPickup) {
+ if (isPickup && !shouldDropPickupEvent()) {
final long timeSinceNotification =
SystemClock.elapsedRealtime() - mNotificationPulseTime;
final boolean withinVibrationThreshold =
@@ -329,6 +338,10 @@
}
}
+ private boolean shouldDropPickupEvent() {
+ return mKeyguardStateController.isOccluded();
+ }
+
private void gentleWakeUp(int reason) {
// Log screen wake up reason (lift/pickup, tap, double-tap)
Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index bfa4780..5b327bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -34,7 +34,7 @@
* See [DumpHandler] for more information on how and when this information is dumped.
*/
@SysUISingleton
-class DumpManager @Inject constructor() {
+open class DumpManager @Inject constructor() {
private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 28f63b0..6561bd5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -25,8 +25,12 @@
* Proxy to make {@link SystemProperties} easily testable.
*/
@SysUISingleton
-class SystemPropertiesHelper @Inject constructor() {
+open class SystemPropertiesHelper @Inject constructor() {
fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
+
+ fun set(name: String, value: Int) {
+ SystemProperties.set(name, value.toString())
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index bc4ced4..1b4a47e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -62,6 +62,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.view.RotationPolicy;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -172,7 +173,8 @@
SysUiState sysUiState,
@Main Handler handler,
PackageManager packageManager,
- StatusBar statusBar) {
+ StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
super(context,
windowManagerFuncs,
@@ -204,7 +206,8 @@
sysUiState,
handler,
packageManager,
- statusBar);
+ statusBar,
+ keyguardUpdateMonitor);
mLockPatternUtils = lockPatternUtils;
mKeyguardStateController = keyguardStateController;
@@ -266,7 +269,7 @@
this::getWalletViewController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(),
- getStatusBar());
+ getStatusBar(), getKeyguardUpdateMonitor(), mLockPatternUtils);
if (shouldShowLockMessage(dialog)) {
dialog.showLockMessage();
@@ -334,12 +337,13 @@
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- StatusBar statusBar) {
+ StatusBar statusBar, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockPatternUtils lockPatternUtils) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
adapter, overflowAdapter, sysuiColorExtractor, statusBarService,
notificationShadeWindowController, sysuiState, onRotateCallback,
keyguardShowing, powerAdapter, uiEventLogger, null,
- statusBar);
+ statusBar, keyguardUpdateMonitor, lockPatternUtils);
mWalletFactory = walletFactory;
// Update window attributes
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 06e7482..9ada54b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -84,6 +84,7 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
@@ -108,6 +109,7 @@
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.animation.Interpolators;
@@ -170,6 +172,11 @@
static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
+ // See NotificationManagerService#scheduleDurationReachedLocked
+ private static final long TOAST_FADE_TIME = 333;
+ // See NotificationManagerService.LONG_DELAY
+ private static final int TOAST_VISIBLE_TIME = 3500;
+
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
private final AudioManager mAudioManager;
@@ -231,6 +238,7 @@
protected Handler mMainHandler;
private int mSmallestScreenWidthDp;
private final StatusBar mStatusBar;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -338,7 +346,8 @@
SysUiState sysUiState,
@Main Handler handler,
PackageManager packageManager,
- StatusBar statusBar) {
+ StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -369,6 +378,7 @@
mMainHandler = handler;
mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
mStatusBar = statusBar;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -422,6 +432,10 @@
return mStatusBar;
}
+ protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
+ return mKeyguardUpdateMonitor;
+ }
+
/**
* Show the global actions dialog (creating if necessary)
*
@@ -653,7 +667,7 @@
mAdapter, mOverflowAdapter, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
- mInfoProvider, mStatusBar);
+ mInfoProvider, mStatusBar, mKeyguardUpdateMonitor, mLockPatternUtils);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
@@ -2122,6 +2136,8 @@
private GlobalActionsInfoProvider mInfoProvider;
private GestureDetector mGestureDetector;
private StatusBar mStatusBar;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private LockPatternUtils mLockPatternUtils;
protected ViewGroup mContainer;
@@ -2173,7 +2189,8 @@
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) {
+ @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
super(context, themeRes);
mContext = context;
mAdapter = adapter;
@@ -2188,6 +2205,8 @@
mUiEventLogger = uiEventLogger;
mInfoProvider = infoProvider;
mStatusBar = statusBar;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
mGestureDetector = new GestureDetector(mContext, mGestureListener);
@@ -2308,6 +2327,14 @@
if (mInfoProvider != null && mInfoProvider.shouldShowMessage()) {
mInfoProvider.addPanel(mContext, mContainer, mAdapter.getCount(), () -> dismiss());
}
+
+ // If user entered from the lock screen and smart lock was enabled, disable it
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
+ if (mKeyguardShowing && userHasTrust) {
+ mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+ showSmartLockDisabledMessage();
+ }
}
protected void fixNavBarClipping() {
@@ -2319,6 +2346,37 @@
contentParent.setClipToPadding(false);
}
+ private void showSmartLockDisabledMessage() {
+ // Since power menu is the top window, make a Toast-like view that will show up
+ View message = LayoutInflater.from(mContext)
+ .inflate(com.android.systemui.R.layout.global_actions_toast, mContainer, false);
+
+ // Set up animation
+ AccessibilityManager mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final int visibleTime = mAccessibilityManager.getRecommendedTimeoutMillis(
+ TOAST_VISIBLE_TIME, AccessibilityManager.FLAG_CONTENT_TEXT);
+ message.setVisibility(View.VISIBLE);
+ message.setAlpha(0f);
+ mContainer.addView(message);
+
+ // Fade in
+ message.animate()
+ .alpha(1f)
+ .setDuration(TOAST_FADE_TIME)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Then fade out
+ message.animate()
+ .alpha(0f)
+ .setDuration(TOAST_FADE_TIME)
+ .setStartDelay(visibleTime);
+ }
+ });
+ }
+
@Override
protected void onStart() {
super.onStart();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index d30783c..a51ec54 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -102,7 +102,6 @@
@Override
public Size reportSurfaceSize() {
- mTexture.use(null /* consumer */);
mSurfaceSize.set(mTexture.getTextureDimensions());
return new Size(mSurfaceSize.width(), mSurfaceSize.height());
}
@@ -124,6 +123,7 @@
private final WallpaperManager mWallpaperManager;
private Bitmap mBitmap;
private boolean mWcgContent;
+ private boolean mTextureUsed;
private WallpaperTexture(WallpaperManager wallpaperManager) {
mWallpaperManager = wallpaperManager;
@@ -141,6 +141,7 @@
mWallpaperManager.forgetLoadedWallpaper();
if (mBitmap != null) {
mDimensions.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ mTextureUsed = true;
} else {
Log.w(TAG, "Can't get bitmap");
}
@@ -171,6 +172,9 @@
}
private Rect getTextureDimensions() {
+ if (!mTextureUsed) {
+ mDimensions.set(mWallpaperManager.peekBitmapDimensions());
+ }
return mDimensions;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 2d215e0..a424674 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -100,8 +100,7 @@
*/
public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
boolean updateImmediately) {
- if (type == INDICATION_TYPE_NOW_PLAYING
- || type == INDICATION_TYPE_REVERSE_CHARGING) {
+ if (type == INDICATION_TYPE_REVERSE_CHARGING) {
// temporarily don't show here, instead use AmbientContainer b/181049781
return;
}
@@ -303,7 +302,6 @@
public static final int INDICATION_TYPE_TRUST = 6;
public static final int INDICATION_TYPE_RESTING = 7;
public static final int INDICATION_TYPE_USER_LOCKED = 8;
- public static final int INDICATION_TYPE_NOW_PLAYING = 9;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
@IntDef({
@@ -317,7 +315,6 @@
INDICATION_TYPE_TRUST,
INDICATION_TYPE_RESTING,
INDICATION_TYPE_USER_LOCKED,
- INDICATION_TYPE_NOW_PLAYING,
INDICATION_TYPE_REVERSE_CHARGING,
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 2facf3d..c743fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -212,14 +212,32 @@
isSsReactivated: Boolean
) {
if (addOrUpdatePlayer(key, oldKey, data)) {
+ // Log card received if a new resumable media card is added
MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ false,
it.surfaceForSmartspaceLogging,
rank = MediaPlayerData.getMediaPlayerIndex(key))
}
}
+ if (isSsReactivated) {
+ // If resumable media is reactivated by headphone connection, update instance
+ // id for each card and log a receive event.
+ MediaPlayerData.players().forEachIndexed { index, it ->
+ if (it.recommendationViewHolder == null) {
+ it.mInstanceId = SmallHash.hash(it.mUid +
+ systemClock.currentTimeMillis().toInt())
+ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
+ it.mInstanceId,
+ it.mUid,
+ /* isRecommendationCard */ false,
+ it.surfaceForSmartspaceLogging,
+ rank = index)
+ }
+ }
+ }
if (mediaCarouselScrollHandler.visibleToUser &&
isSsReactivated && !mediaCarouselScrollHandler.qsExpanded) {
// It could happen that reactived media player isn't visible to user because
@@ -252,6 +270,7 @@
MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ true,
it.surfaceForSmartspaceLogging,
rank = MediaPlayerData.getMediaPlayerIndex(key))
@@ -261,6 +280,7 @@
MediaPlayerData.getMediaPlayerIndex(key)) {
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ true,
it.surfaceForSmartspaceLogging)
}
@@ -339,9 +359,9 @@
if (activeMediaIndex != -1) {
previousVisiblePlayerKey?.let {
val previousVisibleIndex = MediaPlayerData.playerKeys()
- .indexOfFirst { key -> it == key }
+ .indexOfFirst { key -> it == key }
mediaCarouselScrollHandler
- .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
+ .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
} ?: {
mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex)
}
@@ -355,11 +375,11 @@
MediaPlayerData.moveIfExists(oldKey, key)
val existingPlayer = MediaPlayerData.getMediaPlayer(key)
val curVisibleMediaKey = MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
var newPlayer = mediaControlPanelFactory.get()
newPlayer.attachPlayer(
- PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
+ PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
@@ -407,14 +427,14 @@
var newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
+ RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT)
+ ViewGroup.LayoutParams.WRAP_CONTENT)
newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
newRecs.bindRecommendation(data.copy(backgroundColor = bgColor))
val curVisibleMediaKey = MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
MediaPlayerData.addMediaRecommendation(key, data, newRecs, shouldPrioritize, systemClock)
updatePlayerToState(newRecs, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
@@ -462,7 +482,7 @@
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
smartspaceMediaData?.let {
addSmartspaceMediaRecommendations(
- it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
+ it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
}
} else {
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
@@ -585,7 +605,7 @@
?: endShowsActive
if (currentlyShowingOnlyActive != endShowsActive ||
((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
- startShowsActive != endShowsActive)) {
+ startShowsActive != endShowsActive)) {
// Whenever we're transitioning from between differing states or the endstate differs
// we reset the translation
currentlyShowingOnlyActive = endShowsActive
@@ -696,23 +716,43 @@
}
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
mediaControlPanel.mInstanceId,
+ mediaControlPanel.mUid,
isRecommendationCard,
mediaControlPanel.surfaceForSmartspaceLogging)
}
}
@JvmOverloads
+ /**
+ * Log Smartspace events
+ *
+ * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
+ * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
+ * instanceId
+ * @param uid uid for the application that media comes from
+ * @param isRecommendationCard whether the card is media recommendation
+ * @param surface which display surface the media card is on (e.g. lockscreen, shade)
+ * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
+ * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
+ * @param interactedSubcardCardinality how many media items were shown to the user when there
+ * is user interaction
+ * @param rank the rank for media card in the media carousel, starting from 0
+ *
+ */
fun logSmartspaceCardReported(
eventId: Int,
instanceId: Int,
+ uid: Int,
isRecommendationCard: Boolean,
surface: Int,
+ interactedSubcardRank: Int = 0,
+ interactedSubcardCardinality: Int = 0,
rank: Int = mediaCarouselScrollHandler.visibleMediaIndex
) {
// Only log media resume card when Smartspace data is available
if (!isRecommendationCard &&
- !mediaManager.smartspaceMediaData.isActive &&
- MediaPlayerData.smartspaceMediaData == null) {
+ !mediaManager.smartspaceMediaData.isActive &&
+ MediaPlayerData.smartspaceMediaData == null) {
return
}
@@ -720,13 +760,21 @@
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
eventId,
instanceId,
- if (isRecommendationCard)
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS
- else
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_RESUME_MEDIA,
+ // Deprecated, replaced with AiAi feature type so we don't need to create logging
+ // card type for each new feature.
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
surface,
rank,
- mediaContent.getChildCount())
+ mediaContent.getChildCount(),
+ if (isRecommendationCard)
+ 15 // MEDIA_RECOMMENDATION
+ else
+ 31, // MEDIA_RESUME
+ uid,
+ interactedSubcardRank,
+ interactedSubcardCardinality,
+ 0 // received_latency_millis
+ )
/* ktlint-disable max-line-length */
}
@@ -738,18 +786,20 @@
if (!recommendation.isEmpty()) {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
recommendation.get(0).mInstanceId,
+ recommendation.get(0).mUid,
true,
recommendation.get(0).surfaceForSmartspaceLogging,
- /* rank */-1)
+ rank = -1)
} else {
val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
if (MediaPlayerData.players().size > visibleMediaIndex) {
val player = MediaPlayerData.players().elementAt(visibleMediaIndex)
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
player.mInstanceId,
- false,
+ player.mUid,
+ false,
player.surfaceForSmartspaceLogging,
- /* rank */-1)
+ rank = -1)
}
}
mediaManager.onSwipeToDismiss()
@@ -768,7 +818,7 @@
@VisibleForTesting
internal object MediaPlayerData {
private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
- emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+ emptyList(), emptyList(), "INVALID", null, null, null, true, null)
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
@@ -776,18 +826,18 @@
private set
data class MediaSortKey(
- // Whether the item represents a Smartspace media recommendation.
+ // Whether the item represents a Smartspace media recommendation.
val isSsMediaRec: Boolean,
val data: MediaData,
val updateTime: Long = 0
)
private val comparator =
- compareByDescending<MediaSortKey> { it.data.isPlaying }
- .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
- .thenByDescending { it.data.isLocalSession }
- .thenByDescending { !it.data.resumption }
- .thenByDescending { it.updateTime }
+ compareByDescending<MediaSortKey> { it.data.isPlaying }
+ .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
+ .thenByDescending { it.data.isLocalSession }
+ .thenByDescending { !it.data.resumption }
+ .thenByDescending { it.updateTime }
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
@@ -808,7 +858,8 @@
) {
shouldPrioritizeSs = shouldPrioritize
removeMediaPlayer(key)
- val sortKey = MediaSortKey(isSsMediaRec = true, EMPTY, clock.currentTimeMillis())
+ val sortKey = MediaSortKey(/* isSsMediaRec= */ true,
+ EMPTY.copy(isPlaying = false), clock.currentTimeMillis())
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
smartspaceMediaData = data
@@ -888,4 +939,4 @@
}
return false
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 15a7083..e7445f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -33,6 +33,7 @@
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.os.Process;
import android.text.Layout;
import android.util.Log;
import android.view.View;
@@ -74,6 +75,8 @@
private static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
+ private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google"
+ + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity";
private static final String EXTRAS_SMARTSPACE_INTENT =
"com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
private static final int MEDIA_RECOMMENDATION_ITEMS_PER_ROW = 3;
@@ -111,6 +114,9 @@
private int mAlbumArtSize;
// Instance id for logging purpose.
protected int mInstanceId = -1;
+ // Uid for the media app.
+ protected int mUid = Process.INVALID_UID;
+ private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
@@ -248,7 +254,8 @@
openGuts();
return true;
} else {
- return false;
+ closeGuts();
+ return true;
}
});
mRecommendationViewHolder.getCancel().setOnClickListener(v -> {
@@ -266,7 +273,13 @@
}
mKey = key;
MediaSession.Token token = data.getToken();
- mInstanceId = SmallHash.hash(data.getPackageName());
+ PackageManager packageManager = mContext.getPackageManager();
+ try {
+ mUid = packageManager.getApplicationInfo(data.getPackageName(), 0 /* flags */).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to look up package name", e);
+ }
+ mInstanceId = SmallHash.hash(mUid);
mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
@@ -360,27 +373,16 @@
final MediaDeviceData device = data.getDevice();
final int seamlessId = mPlayerViewHolder.getSeamless().getId();
- final int seamlessFallbackId = mPlayerViewHolder.getSeamlessFallback().getId();
- final boolean showFallback = device != null && !device.getEnabled();
- final int seamlessFallbackVisibility = showFallback ? View.VISIBLE : View.GONE;
- mPlayerViewHolder.getSeamlessFallback().setVisibility(seamlessFallbackVisibility);
- expandedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility);
- collapsedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility);
- final int seamlessVisibility = showFallback ? View.GONE : View.VISIBLE;
- mPlayerViewHolder.getSeamless().setVisibility(seamlessVisibility);
- expandedSet.setVisibility(seamlessId, seamlessVisibility);
- collapsedSet.setVisibility(seamlessId, seamlessVisibility);
- final float seamlessAlpha = data.getResumption() ? DISABLED_ALPHA : 1.0f;
+ // Disable clicking on output switcher for invalid devices and resumption controls
+ final boolean seamlessDisabled = (device != null && !device.getEnabled())
+ || data.getResumption();
+ final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
expandedSet.setAlpha(seamlessId, seamlessAlpha);
collapsedSet.setAlpha(seamlessId, seamlessAlpha);
- // Disable clicking on output switcher for resumption controls.
- mPlayerViewHolder.getSeamless().setEnabled(!data.getResumption());
+ mPlayerViewHolder.getSeamless().setEnabled(!seamlessDisabled);
String deviceString = null;
- if (showFallback) {
- iconView.setImageDrawable(null);
- } else if (device != null) {
+ if (device != null && device.getEnabled()) {
Drawable icon = device.getIcon();
- iconView.setVisibility(View.VISIBLE);
if (icon instanceof AdaptiveIcon) {
AdaptiveIcon aIcon = (AdaptiveIcon) icon;
aIcon.setBackgroundColor(mBackgroundColor);
@@ -391,10 +393,9 @@
deviceString = device.getName();
} else {
// Reset to default
- Log.w(TAG, "device is null. Not binding output chip.");
- iconView.setVisibility(View.GONE);
- deviceString = mContext.getString(
- com.android.internal.R.string.ext_media_seamless_action);
+ Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip.");
+ iconView.setImageResource(R.drawable.ic_media_home_devices);
+ deviceString = mContext.getString(R.string.media_seamless_other_device);
}
deviceName.setText(deviceString);
seamlessView.setContentDescription(deviceString);
@@ -531,10 +532,11 @@
}
// Set up recommendation card's header.
- ApplicationInfo applicationInfo = null;
+ ApplicationInfo applicationInfo;
try {
applicationInfo = mContext.getPackageManager()
.getApplicationInfo(data.getPackageName(), 0 /* flags */);
+ mUid = applicationInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Fail to get media recommendation's app info", e);
return;
@@ -553,7 +555,8 @@
headerTitleText.setText(appLabel);
}
// Set up media rec card's tap action if applicable.
- setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction());
+ setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
+ /* interactedSubcardRank */ -1);
// Set up media rec card's accessibility label.
recommendationCard.setContentDescription(
mContext.getString(R.string.controls_media_smartspace_rec_description, appLabel));
@@ -567,7 +570,8 @@
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
int mediaRecommendationNum = Math.min(mediaRecommendationList.size(),
MEDIA_RECOMMENDATION_MAX_NUM);
- for (int itemIndex = 0, uiComponentIndex = 0;
+ int uiComponentIndex = 0;
+ for (int itemIndex = 0;
itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum;
itemIndex++) {
SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex);
@@ -582,7 +586,16 @@
// Set up the media item's click listener if applicable.
ViewGroup mediaCoverContainer = mediaCoverContainers.get(uiComponentIndex);
- setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation);
+ setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation,
+ uiComponentIndex);
+ // Bubble up the long-click event to the card.
+ mediaCoverContainer.setOnLongClickListener(v -> {
+ View parent = (View) v.getParent();
+ if (parent != null) {
+ parent.performLongClick();
+ }
+ return true;
+ });
// Set up the accessibility label for the media item.
String artistName = recommendation.getExtras()
@@ -614,10 +627,10 @@
mediaCoverItemsResIds.get(uiComponentIndex), true);
setVisibleAndAlpha(expandedSet,
mediaCoverContainersResIds.get(uiComponentIndex), true);
-
uiComponentIndex++;
}
+ mSmartspaceMediaItemsCount = uiComponentIndex;
// Set up long press to show guts setting panel.
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
@@ -625,6 +638,22 @@
closeGuts();
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
+
+ Intent dismissIntent = data.getDismissIntent();
+ if (dismissIntent == null) {
+ Log.w(TAG, "Cannot create dismiss action click action: "
+ + "extras missing dismiss_intent.");
+ return;
+ }
+
+ if (dismissIntent.getComponent() != null
+ && dismissIntent.getComponent().getClassName()
+ .equals(EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)) {
+ // Dismiss the card Smartspace data through Smartspace trampoline activity.
+ mContext.startActivity(dismissIntent);
+ } else {
+ mContext.sendBroadcast(dismissIntent);
+ }
});
mController = null;
@@ -750,7 +779,8 @@
private void setSmartspaceRecItemOnClickListener(
@NonNull View view,
- @NonNull SmartspaceAction action) {
+ @NonNull SmartspaceAction action,
+ int interactedSubcardRank) {
if (view == null || action == null || action.getIntent() == null
|| action.getIntent().getExtras() == null) {
Log.e(TAG, "No tap action can be set up");
@@ -758,9 +788,10 @@
}
view.setOnClickListener(v -> {
- // When media recommendation card is shown, it will always be the top card.
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
- /* isRecommendationCard */ true);
+ /* isRecommendationCard */ true,
+ interactedSubcardRank,
+ getSmartspaceSubCardCardinality());
if (shouldSmartspaceRecItemOpenInForeground(action)) {
// Request to unlock the device if the activity needs to be opened in foreground.
@@ -818,9 +849,28 @@
}
private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
+ logSmartspaceCardReported(eventId, isRecommendationCard,
+ /* interactedSubcardRank */ 0,
+ /* interactedSubcardCardinality */ 0);
+ }
+
+ private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard,
+ int interactedSubcardRank, int interactedSubcardCardinality) {
mMediaCarouselController.logSmartspaceCardReported(eventId,
mInstanceId,
+ mUid,
isRecommendationCard,
- getSurfaceForSmartspaceLogging());
+ getSurfaceForSmartspaceLogging(),
+ interactedSubcardRank,
+ interactedSubcardCardinality);
}
-}
+
+ private int getSmartspaceSubCardCardinality() {
+ if (!mMediaCarouselController.getMediaCarouselScrollHandler().getQsExpanded()
+ && mSmartspaceMediaItemsCount > 3) {
+ return 3;
+ }
+
+ return mSmartspaceMediaItemsCount;
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index c8deb01..79206e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.content.Context
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
@@ -32,6 +33,8 @@
private const val TAG = "MediaDataFilter"
private const val DEBUG = true
+private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = ("com.google" +
+ ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity")
private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds"
/**
@@ -51,6 +54,7 @@
* background users (e.g. timeouts).
*/
class MediaDataFilter @Inject constructor(
+ private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
private val mediaResumeListener: MediaResumeListener,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@@ -229,6 +233,18 @@
mediaDataManager.setTimedOut(it, timedOut = true, forceUpdate = true)
}
if (smartspaceMediaData.isActive) {
+ val dismissIntent = smartspaceMediaData.dismissIntent
+ if (dismissIntent == null) {
+ Log.w(TAG, "Cannot create dismiss action click action: " +
+ "extras missing dismiss_intent.")
+ } else if (dismissIntent.getComponent() != null &&
+ dismissIntent.getComponent().getClassName()
+ == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
+ // Dismiss the card Smartspace data through Smartspace trampoline activity.
+ context.startActivity(dismissIntent)
+ } else {
+ context.sendBroadcast(dismissIntent)
+ }
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 0a28b47..eacdab6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -76,12 +76,13 @@
private const val TAG = "MediaDataManager"
private const val DEBUG = true
+private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
- "INVALID", null, emptyList(), 0)
+ "INVALID", null, emptyList(), null, 0)
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
if (!sbn.notification.hasMediaSession()) {
@@ -212,8 +213,8 @@
mediaDataCombineLatest.addListener(mediaDataFilter)
// Set up links back into the pipeline for listeners that need to send events upstream.
- mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
- setTimedOut(token, timedOut) }
+ mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean ->
+ setTimedOut(key, timedOut) }
mediaResumeListener.setManager(this)
mediaDataFilter.mediaDataManager = this
@@ -414,14 +415,18 @@
* This will make the player not active anymore, hiding it from QQS and Keyguard.
* @see MediaData.active
*/
- internal fun setTimedOut(token: String, timedOut: Boolean, forceUpdate: Boolean = false) {
- mediaEntries[token]?.let {
+ internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) {
+ mediaEntries[key]?.let {
if (it.active == !timedOut && !forceUpdate) {
+ if (it.resumption) {
+ if (DEBUG) Log.d(TAG, "timing out resume player $key")
+ dismissMediaData(key, 0L /* delay */)
+ }
return
}
it.active = !timedOut
- if (DEBUG) Log.d(TAG, "Updating $token timedOut: $timedOut")
- onMediaDataLoaded(token, token, it)
+ if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut")
+ onMediaDataLoaded(key, key, it)
}
}
@@ -879,12 +884,22 @@
target: SmartspaceTarget,
isActive: Boolean
): SmartspaceMediaData {
+ var dismissIntent: Intent? = null
+ if (target.baseAction != null && target.baseAction.extras != null) {
+ dismissIntent = target
+ .baseAction
+ .extras
+ .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent
+ }
packageName(target)?.let {
return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it,
- target.baseAction, target.iconGrid, 0)
+ target.baseAction, target.iconGrid,
+ dismissIntent, 0)
}
return EMPTY_SMARTSPACE_MEDIA_DATA
- .copy(targetId = target.smartspaceTargetId, isActive = isActive)
+ .copy(targetId = target.smartspaceTargetId,
+ isActive = isActive,
+ dismissIntent = dismissIntent)
}
private fun packageName(target: SmartspaceTarget): String? {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index ab568c8..608c784 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -35,6 +35,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Utils
+import com.android.systemui.util.time.SystemClock
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.ConcurrentLinkedQueue
@@ -53,11 +54,13 @@
@Background private val backgroundExecutor: Executor,
private val tunerService: TunerService,
private val mediaBrowserFactory: ResumeMediaBrowserFactory,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ private val systemClock: SystemClock
) : MediaDataManager.Listener, Dumpable {
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
- private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
+ private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> =
+ ConcurrentLinkedQueue()
private lateinit var mediaDataManager: MediaDataManager
@@ -131,14 +134,32 @@
val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null)
val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())
?.dropLastWhile { it.isEmpty() }
+ var needsUpdate = false
components?.forEach {
val info = it.split("/")
val packageName = info[0]
val className = info[1]
val component = ComponentName(packageName, className)
- resumeComponents.add(component)
+
+ val lastPlayed = if (info.size == 3) {
+ try {
+ info[2].toLong()
+ } catch (e: NumberFormatException) {
+ needsUpdate = true
+ systemClock.currentTimeMillis()
+ }
+ } else {
+ needsUpdate = true
+ systemClock.currentTimeMillis()
+ }
+ resumeComponents.add(component to lastPlayed)
}
Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
+
+ if (needsUpdate) {
+ // Save any missing times that we had to fill in
+ writeSharedPrefs()
+ }
}
/**
@@ -149,9 +170,12 @@
return
}
+ val now = systemClock.currentTimeMillis()
resumeComponents.forEach {
- val browser = mediaBrowserFactory.create(mediaBrowserCallback, it)
- browser.findRecentMedia()
+ if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
+ val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first)
+ browser.findRecentMedia()
+ }
}
}
@@ -234,18 +258,24 @@
*/
private fun updateResumptionList(componentName: ComponentName) {
// Remove if exists
- resumeComponents.remove(componentName)
+ resumeComponents.remove(resumeComponents.find { it.first.equals(componentName) })
// Insert at front of queue
- resumeComponents.add(componentName)
+ val currentTime = systemClock.currentTimeMillis()
+ resumeComponents.add(componentName to currentTime)
// Remove old components if over the limit
if (resumeComponents.size > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) {
resumeComponents.remove()
}
- // Save changes
+ writeSharedPrefs()
+ }
+
+ private fun writeSharedPrefs() {
val sb = StringBuilder()
resumeComponents.forEach {
- sb.append(it.flattenToString())
+ sb.append(it.first.flattenToString())
+ sb.append("/")
+ sb.append(it.second)
sb.append(ResumeMediaBrowser.DELIMITER)
}
val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9a39193..6f04771 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -20,6 +20,7 @@
import android.media.session.PlaybackState
import android.os.SystemProperties
import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -29,9 +30,15 @@
private const val DEBUG = true
private const val TAG = "MediaTimeout"
-private val PAUSED_MEDIA_TIMEOUT = SystemProperties
+
+@VisibleForTesting
+val PAUSED_MEDIA_TIMEOUT = SystemProperties
.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
+@VisibleForTesting
+val RESUME_MEDIA_TIMEOUT = SystemProperties
+ .getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
+
/**
* Controller responsible for keeping track of playback states and expiring inactive streams.
*/
@@ -45,8 +52,9 @@
/**
* Callback representing that a media object is now expired:
- * @param token Media session unique identifier
- * @param pauseTimeout True when expired for {@code PAUSED_MEDIA_TIMEOUT}
+ * @param key Media control unique identifier
+ * @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media,
+ * or {@code RESUME_MEDIA_TIMEOUT} for resume media
*/
lateinit var timeoutCallback: (String, Boolean) -> Unit
@@ -122,6 +130,7 @@
var timedOut = false
var playing: Boolean? = null
+ var resumption: Boolean? = null
var destroyed = false
var mediaData: MediaData = data
@@ -159,12 +168,19 @@
}
override fun onSessionDestroyed() {
- // If the session is destroyed, the controller is no longer valid, and we will need to
- // recreate it if this key is updated later
if (DEBUG) {
Log.d(TAG, "Session destroyed for $key")
}
- destroy()
+
+ if (resumption == true) {
+ // Some apps create a session when MBS is queried. We should unregister the
+ // controller since it will no longer be valid, but don't cancel the timeout
+ mediaController?.unregisterCallback(this)
+ } else {
+ // For active controls, if the session is destroyed, clean up everything since we
+ // will need to recreate it if this key is updated later
+ destroy()
+ }
}
private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
@@ -173,20 +189,28 @@
}
val isPlaying = state != null && isPlayingState(state.state)
- if (playing == isPlaying && playing != null) {
+ val resumptionChanged = resumption != mediaData.resumption
+ if (playing == isPlaying && playing != null && !resumptionChanged) {
return
}
playing = isPlaying
+ resumption = mediaData.resumption
if (!isPlaying) {
if (DEBUG) {
- Log.v(TAG, "schedule timeout for $key")
+ Log.v(TAG, "schedule timeout for $key playing $isPlaying, $resumption")
}
- if (cancellation != null) {
+ if (cancellation != null && !resumptionChanged) {
+ // if the media changed resume state, we'll need to adjust the timeout length
if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.")
return
}
- expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state")
+ expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
+ val timeout = if (mediaData.resumption) {
+ RESUME_MEDIA_TIMEOUT
+ } else {
+ PAUSED_MEDIA_TIMEOUT
+ }
cancellation = mainExecutor.executeDelayed({
cancellation = null
if (DEBUG) {
@@ -195,7 +219,7 @@
timedOut = true
// this event is async, so it's safe even when `dispatchEvents` is false
timeoutCallback(key, timedOut)
- }, PAUSED_MEDIA_TIMEOUT)
+ }, timeout)
} else {
expireMediaTimeout(key, "playback started - $state, $key")
timedOut = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 791f59d..35603b6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -43,7 +43,6 @@
val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
- val seamlessFallback = itemView.requireViewById<ImageView>(R.id.media_seamless_fallback)
// Seek bar
val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
@@ -124,7 +123,6 @@
R.id.header_title,
R.id.header_artist,
R.id.media_seamless,
- R.id.media_seamless_fallback,
R.id.notification_media_progress_time,
R.id.media_progress_bar,
R.id.action0,
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
index 9ac1289..61fdefd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
+import android.content.Intent
/** State of a Smartspace media recommendations view. */
data class SmartspaceMediaData(
@@ -45,6 +46,10 @@
*/
val recommendations: List<SmartspaceAction>,
/**
+ * Intent for the user's initiated dismissal.
+ */
+ val dismissIntent: Intent?,
+ /**
* View's background color.
*/
val backgroundColor: Int
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
index b6c2ef1..140a1fe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
@@ -1,10 +1,13 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceTarget
+import android.util.Log
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import javax.inject.Inject
+private const val TAG = "SsMediaDataProvider"
+
/** Provides SmartspaceTargets of media types for SystemUI media control. */
class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin {
@@ -31,6 +34,10 @@
}
}
+ if (!mediaTargets.isEmpty()) {
+ Log.d(TAG, "Forwarding Smartspace media updates $mediaTargets")
+ }
+
smartspaceMediaTargets = mediaTargets
smartspaceMediaTargetListeners.forEach {
it.onSmartspaceTargetsUpdated(smartspaceMediaTargets)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index cb11454..8e6eb02 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -410,6 +410,12 @@
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
+ // TODO(b/198002034): Content observers currently can still be called back after being
+ // unregistered, and in this case we can ignore the change if the nav bar has been
+ // destroyed already
+ if (mNavigationBarView == null) {
+ return;
+ }
updateAssistantEntrypoints();
}
};
@@ -601,6 +607,7 @@
}
public void destroyView() {
+ setAutoHideController(/* autoHideController */ null);
mCommandQueue.removeCallback(this);
mContext.getSystemService(WindowManager.class).removeViewImmediate(
mNavigationBarView.getRootView());
@@ -651,7 +658,7 @@
if (mIsOnDefaultDisplay) {
final RotationButtonController rotationButtonController =
mNavigationBarView.getRotationButtonController();
- rotationButtonController.addRotationCallback(mRotationWatcher);
+ rotationButtonController.setRotationCallback(mRotationWatcher);
// Reset user rotation pref to match that of the WindowManager if starting in locked
// mode. This will automatically happen when switching from auto-rotate to locked mode.
@@ -690,6 +697,9 @@
@Override
public void onViewDetachedFromWindow(View v) {
+ final RotationButtonController rotationButtonController =
+ mNavigationBarView.getRotationButtonController();
+ rotationButtonController.setRotationCallback(null);
mNavigationBarView.getBarTransitions().destroy();
mNavigationBarView.getLightTransitionsController().destroy(mContext);
mOverviewProxyService.removeCallback(mOverviewProxyListener);
@@ -937,6 +947,11 @@
@Override
public void onRotationProposal(final int rotation, boolean isValid) {
+ // The CommandQueue callbacks are added when the view is created to ensure we track other
+ // states, but until the view is attached (at the next traversal), the view's display is
+ // not valid. Just ignore the rotation in this case.
+ if (!mNavigationBarView.isAttachedToWindow()) return;
+
final int winRotation = mNavigationBarView.getDisplay().getRotation();
final boolean rotateSuggestionsDisabled = RotationButtonController
.hasDisable2RotateSuggestionFlag(mDisabledFlags2);
@@ -1516,7 +1531,7 @@
}
/** Sets {@link AutoHideController} to the navigation bar. */
- public void setAutoHideController(AutoHideController autoHideController) {
+ private void setAutoHideController(AutoHideController autoHideController) {
mAutoHideController = autoHideController;
if (mAutoHideController != null) {
mAutoHideController.setNavigationBar(mAutoHideUiElement);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index b9e9240..1628c71 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -395,7 +395,6 @@
void removeNavigationBar(int displayId) {
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
- navBar.setAutoHideController(/* autoHideController */ null);
navBar.destroyView();
mNavigationBars.remove(displayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 61e8033..70c21e4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -101,10 +101,7 @@
public class NavigationBarView extends FrameLayout implements
NavigationModeController.ModeChangedListener {
final static boolean DEBUG = false;
- final static String TAG = "StatusBar/NavBarView";
-
- // slippery nav bar when everything is disabled, e.g. during setup
- final static boolean SLIPPERY_WHEN_DISABLED = true;
+ final static String TAG = "NavBarView";
final static boolean ALTERNATE_CAR_MODE_UI = false;
private final RegionSamplingHelper mRegionSamplingHelper;
@@ -274,7 +271,7 @@
};
private final Consumer<Boolean> mRotationButtonListener = (visible) -> {
- if (visible) {
+ if (visible && mAutoHideController != null) {
// If the button will actually become visible and the navbar is about to hide,
// tell the statusbar to keep it around for longer
mAutoHideController.touchAutoHide();
@@ -373,6 +370,12 @@
}
}
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ Log.e(TAG, "onSetAlpha", new Throwable());
+ return super.onSetAlpha(alpha);
+ }
+
public void setAutoHideController(AutoHideController autoHideController) {
mAutoHideController = autoHideController;
}
@@ -1313,6 +1316,7 @@
dumpButton(pw, "back", getBackButton());
dumpButton(pw, "home", getHomeButton());
+ dumpButton(pw, "handle", getHomeHandle());
dumpButton(pw, "rcnt", getRecentsButton());
dumpButton(pw, "rota", getRotateSuggestionButton());
dumpButton(pw, "a11y", getAccessibilityButton());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 649ac87..8ea9ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -180,7 +180,7 @@
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
- void addRotationCallback(Consumer<Integer> watcher) {
+ void setRotationCallback(Consumer<Integer> watcher) {
mRotWatcherListener = watcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index a626681..d0aa710 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -29,6 +29,7 @@
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -67,6 +68,7 @@
private val privacyLogger: PrivacyLogger,
private val keyguardStateController: KeyguardStateController,
private val appOpsController: AppOpsController,
+ private val uiEventLogger: UiEventLogger,
@VisibleForTesting private val dialogProvider: DialogProvider
) {
@@ -81,7 +83,8 @@
@Main uiExecutor: Executor,
privacyLogger: PrivacyLogger,
keyguardStateController: KeyguardStateController,
- appOpsController: AppOpsController
+ appOpsController: AppOpsController,
+ uiEventLogger: UiEventLogger
) : this(
permissionManager,
packageManager,
@@ -93,6 +96,7 @@
privacyLogger,
keyguardStateController,
appOpsController,
+ uiEventLogger,
defaultDialogProvider
)
@@ -105,6 +109,7 @@
private val onDialogDismissed = object : PrivacyDialog.OnDialogDismissed {
override fun onDialogDismissed() {
privacyLogger.logPrivacyDialogDismissed()
+ uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
dialog = null
}
}
@@ -114,6 +119,8 @@
val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
+ uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+ userId, packageName)
privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
if (!keyguardStateController.isUnlocked) {
// If we are locked, hide the dialog so the user can unlock
@@ -155,7 +162,7 @@
val items = usage.mapNotNull {
val type = filterType(permGroupToPrivacyType(it.permGroupName))
val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
- userInfo?.let { ui ->
+ if (userInfo != null || it.isPhoneCall) {
type?.let { t ->
// Only try to get the app name if we actually need it
val appName = if (it.isPhoneCall) {
@@ -171,10 +178,14 @@
it.attribution,
it.lastAccess,
it.isActive,
- ui.isManagedProfile,
+ // If there's no user info, we're in a phoneCall in secondary user
+ userInfo?.isManagedProfile ?: false,
it.isPhoneCall
)
}
+ } else {
+ // No matching user or phone call
+ null
}
}
uiExecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
new file mode 100644
index 0000000..3ecc5a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.privacy
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.")
+ PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904),
+
+ @UiEvent(doc = "Privacy dialog is dismissed by user.")
+ PRIVACY_DIALOG_DISMISSED(905);
+
+ override fun getId() = _id
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
deleted file mode 100644
index 38b20ee..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.qs;
-
-import static com.android.systemui.statusbar.phone.AutoTileManager.HOTSPOT;
-import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION;
-import static com.android.systemui.statusbar.phone.AutoTileManager.NIGHT;
-import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
-import com.android.systemui.util.UserAwareController;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-import javax.inject.Inject;
-
-public class AutoAddTracker implements UserAwareController {
-
- private static final String[][] CONVERT_PREFS = {
- {Key.QS_HOTSPOT_ADDED, HOTSPOT},
- {Key.QS_DATA_SAVER_ADDED, SAVER},
- {Key.QS_INVERT_COLORS_ADDED, INVERSION},
- {Key.QS_WORK_ADDED, WORK},
- {Key.QS_NIGHTDISPLAY_ADDED, NIGHT},
- };
-
- private final ArraySet<String> mAutoAdded;
- private final Context mContext;
- private int mUserId;
-
- public AutoAddTracker(Context context, int userId) {
- mContext = context;
- mUserId = userId;
- mAutoAdded = new ArraySet<>(getAdded());
- }
-
- /**
- * Init method must be called after construction to start listening
- */
- public void initialize() {
- // TODO: remove migration code and shared preferences keys after P release
- if (mUserId == UserHandle.USER_SYSTEM) {
- for (String[] convertPref : CONVERT_PREFS) {
- if (Prefs.getBoolean(mContext, convertPref[0], false)) {
- setTileAdded(convertPref[1]);
- Prefs.remove(mContext, convertPref[0]);
- }
- }
- }
- mContext.getContentResolver().registerContentObserver(
- Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver,
- UserHandle.USER_ALL);
- }
-
- @Override
- public void changeUser(UserHandle newUser) {
- if (newUser.getIdentifier() == mUserId) {
- return;
- }
- mUserId = newUser.getIdentifier();
- mAutoAdded.clear();
- mAutoAdded.addAll(getAdded());
- }
-
- @Override
- public int getCurrentUserId() {
- return mUserId;
- }
-
- public boolean isAdded(String tile) {
- return mAutoAdded.contains(tile);
- }
-
- public void setTileAdded(String tile) {
- if (mAutoAdded.add(tile)) {
- saveTiles();
- }
- }
-
- public void setTileRemoved(String tile) {
- if (mAutoAdded.remove(tile)) {
- saveTiles();
- }
- }
-
- public void destroy() {
- mContext.getContentResolver().unregisterContentObserver(mObserver);
- }
-
- private void saveTiles() {
- Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES,
- TextUtils.join(",", mAutoAdded), mUserId);
- }
-
- private Collection<String> getAdded() {
- String current = Secure.getStringForUser(mContext.getContentResolver(),
- Secure.QS_AUTO_ADDED_TILES, mUserId);
- if (current == null) {
- return Collections.emptyList();
- }
- return Arrays.asList(current.split(","));
- }
-
- @VisibleForTesting
- protected final ContentObserver mObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- mAutoAdded.clear();
- mAutoAdded.addAll(getAdded());
- }
- };
-
- public static class Builder {
- private final Context mContext;
- private int mUserId;
-
- @Inject
- public Builder(Context context) {
- mContext = context;
- }
-
- public Builder setUserId(int userId) {
- mUserId = userId;
- return this;
- }
-
- public AutoAddTracker build() {
- return new AutoAddTracker(mContext, mUserId);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
new file mode 100644
index 0000000..7ffa9d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 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.qs
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.text.TextUtils
+import android.util.ArraySet
+import android.util.Log
+import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.UserAwareController
+import com.android.systemui.util.settings.SecureSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "AutoAddTracker"
+
+/**
+ * Class to track tiles that have been auto-added
+ *
+ * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES].
+ *
+ * It also handles restore gracefully.
+ */
+class AutoAddTracker @VisibleForTesting constructor(
+ private val secureSettings: SecureSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val qsHost: QSHost,
+ private val dumpManager: DumpManager,
+ private val mainHandler: Handler?,
+ private val backgroundExecutor: Executor,
+ private var userId: Int
+) : UserAwareController, Dumpable {
+
+ companion object {
+ private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
+ }
+
+ @GuardedBy("autoAdded")
+ private val autoAdded = ArraySet<String>()
+ private var restoredTiles: Set<String>? = null
+
+ override val currentUserId: Int
+ get() = userId
+
+ private val contentObserver = object : ContentObserver(mainHandler) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: Collection<Uri>,
+ flags: Int,
+ _userId: Int
+ ) {
+ if (_userId != userId) {
+ // Ignore changes outside of our user. We'll load the correct value on user change
+ return
+ }
+ loadTiles()
+ }
+ }
+
+ private val restoreReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != Intent.ACTION_SETTING_RESTORED) return
+ processRestoreIntent(intent)
+ }
+ }
+
+ private fun processRestoreIntent(intent: Intent) {
+ when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
+ Settings.Secure.QS_TILES -> {
+ restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+ ?.split(",")
+ ?.toSet()
+ ?: run {
+ Log.w(TAG, "Null restored tiles for user $userId")
+ emptySet()
+ }
+ }
+ Settings.Secure.QS_AUTO_ADDED_TILES -> {
+ restoredTiles?.let { tiles ->
+ val restoredAutoAdded = intent
+ .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+ ?.split(",")
+ ?: emptyList()
+ val autoAddedBeforeRestore = intent
+ .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)
+ ?.split(",")
+ ?: emptyList()
+
+ val tilesToRemove = restoredAutoAdded.filter { it !in tiles }
+ if (tilesToRemove.isNotEmpty()) {
+ qsHost.removeTiles(tilesToRemove)
+ }
+ val tiles = synchronized(autoAdded) {
+ autoAdded.clear()
+ autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
+ getTilesFromListLocked()
+ }
+ saveTiles(tiles)
+ } ?: run {
+ Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
+ "${Settings.Secure.QS_TILES} for user $userId")
+ }
+ }
+ else -> {} // Do nothing for other Settings
+ }
+ }
+
+ /**
+ * Init method must be called after construction to start listening
+ */
+ fun initialize() {
+ dumpManager.registerDumpable(TAG, this)
+ loadTiles()
+ secureSettings.registerContentObserverForUser(
+ secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
+ contentObserver,
+ UserHandle.USER_ALL
+ )
+ registerBroadcastReceiver()
+ }
+
+ /**
+ * Unregister listeners, receivers and observers
+ */
+ fun destroy() {
+ dumpManager.unregisterDumpable(TAG)
+ secureSettings.unregisterContentObserver(contentObserver)
+ unregisterBroadcastReceiver()
+ }
+
+ private fun registerBroadcastReceiver() {
+ broadcastDispatcher.registerReceiver(
+ restoreReceiver,
+ FILTER,
+ backgroundExecutor,
+ UserHandle.of(userId)
+ )
+ }
+
+ private fun unregisterBroadcastReceiver() {
+ broadcastDispatcher.unregisterReceiver(restoreReceiver)
+ }
+
+ override fun changeUser(newUser: UserHandle) {
+ if (newUser.identifier == userId) return
+ unregisterBroadcastReceiver()
+ userId = newUser.identifier
+ restoredTiles = null
+ loadTiles()
+ registerBroadcastReceiver()
+ }
+
+ /**
+ * Returns `true` if the tile has been auto-added before
+ */
+ fun isAdded(tile: String): Boolean {
+ return synchronized(autoAdded) {
+ tile in autoAdded
+ }
+ }
+
+ /**
+ * Sets a tile as auto-added.
+ *
+ * From here on, [isAdded] will return true for that tile.
+ */
+ fun setTileAdded(tile: String) {
+ val tiles = synchronized(autoAdded) {
+ if (autoAdded.add(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
+ }
+ }
+ tiles?.let { saveTiles(it) }
+ }
+
+ /**
+ * Removes a tile from the list of auto-added.
+ *
+ * This allows for this tile to be auto-added again in the future.
+ */
+ fun setTileRemoved(tile: String) {
+ val tiles = synchronized(autoAdded) {
+ if (autoAdded.remove(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
+ }
+ }
+ tiles?.let { saveTiles(it) }
+ }
+
+ private fun getTilesFromListLocked(): String {
+ return TextUtils.join(",", autoAdded)
+ }
+
+ private fun saveTiles(tiles: String) {
+ secureSettings.putStringForUser(
+ Settings.Secure.QS_AUTO_ADDED_TILES,
+ tiles,
+ /* tag */ null,
+ /* makeDefault */ false,
+ userId,
+ /* overrideableByRestore */ true
+ )
+ }
+
+ private fun loadTiles() {
+ synchronized(autoAdded) {
+ autoAdded.clear()
+ autoAdded.addAll(getAdded())
+ }
+ }
+
+ private fun getAdded(): Collection<String> {
+ val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
+ return current?.split(",") ?: emptySet()
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("Current user: $userId")
+ pw.println("Added tiles: $autoAdded")
+ }
+
+ @SysUISingleton
+ class Builder @Inject constructor(
+ private val secureSettings: SecureSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val qsHost: QSHost,
+ private val dumpManager: DumpManager,
+ @Main private val handler: Handler,
+ @Background private val executor: Executor
+ ) {
+ private var userId: Int = 0
+
+ fun setUserId(_userId: Int): Builder {
+ userId = _userId
+ return this
+ }
+
+ fun build(): AutoAddTracker {
+ return AutoAddTracker(
+ secureSettings,
+ broadcastDispatcher,
+ qsHost,
+ dumpManager,
+ handler,
+ executor,
+ userId
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 59e5eb8..6f12e46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,6 +23,7 @@
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
+import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
@@ -289,6 +290,16 @@
}
}
+ @Override
+ protected boolean isTransformedTouchPointInView(float x, float y,
+ View child, PointF outLocalPoint) {
+ // Prevent touches outside the clipped area from propagating to a child in that area.
+ if (mClippingEnabled && y + getTranslationY() > mFancyClippingTop) {
+ return false;
+ }
+ return super.isTransformedTouchPointInView(x, y, child, outLocalPoint);
+ }
+
private void updateClippingPath() {
mFancyClippingPath.reset();
if (!mClippingEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 000fd1c..9f585bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -37,6 +37,7 @@
void removeCallback(Callback callback);
TileServices getTileServices();
void removeTile(String tileSpec);
+ void removeTiles(Collection<String> specs);
void unmarkTileAsAutoAdded(String tileSpec);
int indexOf(String tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 4739a3f..08cb4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.metrics.LogMaker;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
@@ -80,7 +81,8 @@
private final QSHost.Callback mQSHostCallback = this::setTiles;
- private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
+ @VisibleForTesting
+ protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
@@ -156,6 +158,7 @@
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
mHost.addCallback(mQSHostCallback);
setTiles();
+ mLastOrientation = getResources().getConfiguration().orientation;
switchTileLayout(true);
mDumpManager.registerDumpable(mView.getDumpableTag(), this);
@@ -356,8 +359,7 @@
return false;
}
return mUsingMediaPlayer && mMediaHost.getVisible()
- && getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
}
private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 4a75810..e60fb49 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -351,6 +351,17 @@
changeTileSpecs(tileSpecs-> tileSpecs.remove(spec));
}
+ /**
+ * Remove many tiles at once.
+ *
+ * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called
+ * multiple times).
+ */
+ @Override
+ public void removeTiles(Collection<String> specs) {
+ changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs));
+ }
+
@Override
public void unmarkTileAsAutoAdded(String spec) {
if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
@@ -372,6 +383,7 @@
* @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
*/
public void addTile(String spec, int requestPosition) {
+ if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile");
changeTileSpecs(tileSpecs -> {
if (tileSpecs.contains(spec)) return false;
@@ -386,6 +398,7 @@
}
void saveTilesToSettings(List<String> tileSpecs) {
+ if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile");
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 77906ab..84b961e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.VariableDateView;
import java.util.List;
@@ -62,11 +63,14 @@
protected QuickQSPanel mHeaderQsPanel;
private View mDatePrivacyView;
private View mDateView;
+ // DateView next to clock. Visible on QQS
+ private VariableDateView mClockDateView;
private View mSecurityHeaderView;
private View mClockIconsView;
private View mContainer;
private View mQSCarriers;
+ private ViewGroup mClockContainer;
private Clock mClockView;
private Space mDatePrivacySeparator;
private View mClockIconsSeparator;
@@ -86,7 +90,6 @@
private int mWaterfallTopInset;
private int mCutOutPaddingLeft;
private int mCutOutPaddingRight;
- private float mViewAlpha = 1.0f;
private float mKeyguardExpansionFraction;
private int mTextColorPrimary = Color.TRANSPARENT;
private int mTopViewMeasureHeight;
@@ -123,18 +126,24 @@
mIconContainer = findViewById(R.id.statusIcons);
mPrivacyChip = findViewById(R.id.privacy_chip);
mDateView = findViewById(R.id.date);
+ mClockDateView = findViewById(R.id.date_clock);
mSecurityHeaderView = findViewById(R.id.header_text_container);
mClockIconsSeparator = findViewById(R.id.separator);
mRightLayout = findViewById(R.id.rightLayout);
mDateContainer = findViewById(R.id.date_container);
mPrivacyContainer = findViewById(R.id.privacy_container);
+ mClockContainer = findViewById(R.id.clock_container);
mClockView = findViewById(R.id.clock);
mDatePrivacySeparator = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
updateResources();
+ Configuration config = mContext.getResources().getConfiguration();
+ setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ setSecurityHeaderContainerVisibility(
+ config.orientation == Configuration.ORIENTATION_LANDSCAPE);
// Don't need to worry about tuner settings for this icon
mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
@@ -177,7 +186,7 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) {
mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight();
- updateAnimators();
+ post(this::updateAnimators);
}
}
@@ -186,6 +195,8 @@
super.onConfigurationChanged(newConfig);
updateResources();
setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ setSecurityHeaderContainerVisibility(
+ newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
@Override
@@ -206,6 +217,10 @@
mPrivacyContainer.setLayoutParams(lp);
}
+ private void setSecurityHeaderContainerVisibility(boolean landscape) {
+ mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE);
+ }
+
private void updateBatteryMode() {
if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
@@ -262,6 +277,25 @@
updateBatteryMode();
updateHeadersPadding();
updateAnimators();
+
+ updateClockDatePadding();
+ }
+
+ private void updateClockDatePadding() {
+ int startPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding);
+ int endPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding);
+ mClockView.setPaddingRelative(
+ startPadding,
+ mClockView.getPaddingTop(),
+ endPadding,
+ mClockView.getPaddingBottom()
+ );
+
+ MarginLayoutParams lp = (MarginLayoutParams) mClockDateView.getLayoutParams();
+ lp.setMarginStart(endPadding);
+ mClockDateView.setLayoutParams(lp);
}
private void updateAnimators() {
@@ -280,7 +314,8 @@
TouchAnimator.Builder builder = new TouchAnimator.Builder()
.addFloat(mSecurityHeaderView, "alpha", 0, 1)
// These views appear on expanding down
- .addFloat(mClockView, "alpha", 0, 1)
+ .addFloat(mDateView, "alpha", 0, 0, 1)
+ .addFloat(mClockDateView, "alpha", 1, 0, 0)
.addFloat(mQSCarriers, "alpha", 0, 1)
.setListener(new TouchAnimator.ListenerAdapter() {
@Override
@@ -289,10 +324,14 @@
if (!mIsSingleCarrier) {
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
}
+ // Make it gone so there's enough room for carrier names
+ mClockDateView.setVisibility(View.GONE);
}
@Override
public void onAnimationStarted() {
+ mClockDateView.setVisibility(View.VISIBLE);
+ mClockDateView.setFreezeSwitching(true);
setSeparatorVisibility(false);
if (!mIsSingleCarrier) {
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
@@ -302,6 +341,8 @@
@Override
public void onAnimationAtStart() {
super.onAnimationAtStart();
+ mClockDateView.setFreezeSwitching(false);
+ mClockDateView.setVisibility(View.VISIBLE);
setSeparatorVisibility(mShowClockIconsSeparator);
// In QQS we never ignore RSSI.
mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots);
@@ -434,10 +475,11 @@
mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE);
mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams();
+ LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) mClockContainer.getLayoutParams();
lp.width = visible ? 0 : WRAP_CONTENT;
lp.weight = visible ? 1f : 0f;
- mClockView.setLayoutParams(lp);
+ mClockContainer.setLayoutParams(lp);
lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams();
lp.width = visible ? 0 : WRAP_CONTENT;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index da75c9e..18d6e64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.VariableDateViewController;
import com.android.systemui.util.ViewController;
import java.util.List;
@@ -71,6 +72,9 @@
private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
private final FeatureFlags mFeatureFlags;
+ private final VariableDateViewController mVariableDateViewControllerDateView;
+ private final VariableDateViewController mVariableDateViewControllerClockDateView;
+
private boolean mListening;
private boolean mMicCameraIndicatorsEnabled;
private boolean mLocationIndicatorsEnabled;
@@ -134,7 +138,8 @@
SysuiColorExtractor colorExtractor,
PrivacyDialogController privacyDialogController,
QSExpansionPathInterpolator qsExpansionPathInterpolator,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ VariableDateViewController.Factory variableDateViewControllerFactory) {
super(view);
mPrivacyItemController = privacyItemController;
mActivityStarter = activityStarter;
@@ -154,6 +159,12 @@
mPrivacyChip = mView.findViewById(R.id.privacy_chip);
mClockView = mView.findViewById(R.id.clock);
mIconContainer = mView.findViewById(R.id.statusIcons);
+ mVariableDateViewControllerDateView = variableDateViewControllerFactory.create(
+ mView.requireViewById(R.id.date)
+ );
+ mVariableDateViewControllerClockDateView = variableDateViewControllerFactory.create(
+ mView.requireViewById(R.id.date_clock)
+ );
mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
@@ -205,6 +216,9 @@
mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots);
mDemoModeController.addCallback(mDemoModeReceiver);
+
+ mVariableDateViewControllerDateView.init();
+ mVariableDateViewControllerClockDateView.init();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 2e771d6..b1cd03c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -116,6 +116,9 @@
: icon.getInvisibleDrawable(mContext) : null;
int padding = icon != null ? icon.getPadding() : 0;
if (d != null) {
+ if (d.getConstantState() != null) {
+ d = d.getConstantState().newDrawable();
+ }
d.setAutoMirrored(false);
d.setLayoutDirection(getLayoutDirection());
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 73d1370..14e0f70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -54,7 +54,7 @@
private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null
private val icon = ResourceIcon.get(R.drawable.ic_alarm)
@VisibleForTesting
- internal val defaultIntent = Intent(AlarmClock.ACTION_SET_ALARM)
+ internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
lastAlarmInfo = nextAlarm
refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 4b13015..04f089d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -150,11 +150,7 @@
}
List<CastDevice> activeDevices = getActiveDevices();
- // We want to pop up the media route selection dialog if we either have no active devices
- // (neither routes nor projection), or if we have an active route. In other cases, we assume
- // that a projection is active. This is messy, but this tile never correctly handled the
- // case where multiple devices were active :-/.
- if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) {
+ if (willPopDetail()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
showDetail(true);
});
@@ -163,6 +159,15 @@
}
}
+ // We want to pop up the media route selection dialog if we either have no active devices
+ // (neither routes nor projection), or if we have an active route. In other cases, we assume
+ // that a projection is active. This is messy, but this tile never correctly handled the
+ // case where multiple devices were active :-/.
+ private boolean willPopDetail() {
+ List<CastDevice> activeDevices = getActiveDevices();
+ return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo);
+ }
+
private List<CastDevice> getActiveDevices() {
ArrayList<CastDevice> activeDevices = new ArrayList<>();
for (CastDevice device : mController.getCastDevices()) {
@@ -234,10 +239,12 @@
state.contentDescription = state.contentDescription + ","
+ mContext.getString(R.string.accessibility_quick_settings_open_details);
state.expandedAccessibilityClassName = Button.class.getName();
+ state.forceExpandIcon = willPopDetail();
} else {
state.state = Tile.STATE_UNAVAILABLE;
String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
state.secondaryLabel = noWifi;
+ state.forceExpandIcon = false;
}
state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
mDetailAdapter.updateItems(devices);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 34f2b63..87edc2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -47,6 +47,7 @@
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTileImpl<BooleanState> {
+
private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
private final HotspotController mHotspotController;
@@ -98,7 +99,7 @@
@Override
public Intent getLongClickIntent() {
- return new Intent(Settings.ACTION_TETHER_SETTINGS);
+ return new Intent(Settings.ACTION_WIFI_TETHER_SETTING);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 7cb1421..cc9e748 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,7 +51,9 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -66,15 +68,16 @@
/** Quick settings tile: Internet **/
public class InternetTile extends QSTileImpl<SignalState> {
private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
- private static final Intent INTERNET_PANEL =
- new Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
protected final NetworkController mController;
+ private final AccessPointController mAccessPointController;
private final DataUsageController mDataController;
// The last updated tile state, 0: mobile, 1: wifi, 2: ethernet.
private int mLastTileState = -1;
protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback();
+ private final InternetDialogFactory mInternetDialogFactory;
+ final Handler mHandler;
@Inject
public InternetTile(
@@ -86,11 +89,16 @@
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- NetworkController networkController
+ NetworkController networkController,
+ AccessPointController accessPointController,
+ InternetDialogFactory internetDialogFactory
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
+ mInternetDialogFactory = internetDialogFactory;
+ mHandler = mainHandler;
mController = networkController;
+ mAccessPointController = accessPointController;
mDataController = mController.getMobileDataController();
mController.observe(getLifecycle(), mSignalCallback);
}
@@ -114,7 +122,9 @@
@Override
protected void handleClick(@Nullable View view) {
- mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0);
+ mHandler.post(() -> mInternetDialogFactory.create(true,
+ mAccessPointController.canConfigMobileData(),
+ mAccessPointController.canConfigWifi()));
}
@Override
@@ -429,7 +439,7 @@
state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
}
} else if (cb.mNoDefaultNetwork) {
- if (cb.mNoNetworksAvailable) {
+ if (cb.mNoNetworksAvailable || !cb.mEnabled) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
@@ -489,7 +499,7 @@
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (cb.mNoDefaultNetwork) {
- if (cb.mNoNetworksAvailable) {
+ if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 0bbb5bd..4e936b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -16,12 +16,19 @@
package com.android.systemui.qs.tiles;
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.Manifest;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.view.View;
import android.widget.Switch;
@@ -38,18 +45,25 @@
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
+import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
/** Quick settings tile: Rotation **/
-public class RotationLockTile extends QSTileImpl<BooleanState> {
+public class RotationLockTile extends QSTileImpl<BooleanState> implements
+ BatteryController.BatteryStateChangeCallback {
private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
private final RotationLockController mController;
+ private final SensorPrivacyManager mPrivacyManager;
+ private final BatteryController mBatteryController;
+ private final SecureSetting mSetting;
@Inject
public RotationLockTile(
@@ -61,12 +75,38 @@
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- RotationLockController rotationLockController
+ RotationLockController rotationLockController,
+ SensorPrivacyManager privacyManager,
+ BatteryController batteryController,
+ SecureSettings secureSettings
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = rotationLockController;
mController.observe(this, mCallback);
+ mPrivacyManager = privacyManager;
+ mBatteryController = batteryController;
+ mPrivacyManager
+ .addSensorPrivacyListener(CAMERA, (sensor, enabled) -> refreshState());
+ int currentUser = host.getUserContext().getUserId();
+ mSetting = new SecureSetting(
+ secureSettings,
+ mHandler,
+ Secure.CAMERA_AUTOROTATE,
+ currentUser
+ ) {
+ @Override
+ protected void handleValueChanged(int value, boolean observedChange) {
+ // mHandler is the background handler so calling this is OK
+ handleRefreshState(value);
+ }
+ };
+ mBatteryController.observe(getLifecycle(), this);
+ }
+
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ refreshState();
}
@Override
@@ -95,14 +135,33 @@
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean rotationLocked = mController.isRotationLocked();
+ final boolean powerSave = mBatteryController.isPowerSave();
+ final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(
+ SensorPrivacyManager.Sensors.CAMERA);
+ final boolean cameraRotation =
+ !powerSave && !cameraLocked && hasSufficientPermission(mContext)
+ && mController.isCameraRotationEnabled();
state.value = !rotationLocked;
state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
state.icon = mIcon;
state.contentDescription = getAccessibilityString(rotationLocked);
+ if (!rotationLocked && cameraRotation) {
+ state.secondaryLabel = mContext.getResources().getString(
+ R.string.rotation_lock_camera_rotation_on);
+ } else {
+ state.secondaryLabel = "";
+ }
+
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ mSetting.setUserId(newUserId);
+ handleRefreshState(mSetting.getValue());
+ }
+
public static boolean isCurrentOrientationLockPortrait(RotationLockController controller,
Resources resources) {
int lockOrientation = controller.getRotationLockOrientation();
@@ -140,4 +199,11 @@
refreshState(rotationLocked);
}
};
+
+ private boolean hasSufficientPermission(Context context) {
+ final PackageManager packageManager = context.getPackageManager();
+ final String rotationPackage = packageManager.getRotationResolverPackageName();
+ return rotationPackage != null && packageManager.checkPermission(
+ Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
new file mode 100644
index 0000000..99eb5b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 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.qs.tiles.dialog;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.Utils;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Adapter for showing Wi-Fi networks.
+ */
+public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> {
+
+ private static final String TAG = "InternetAdapter";
+ private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
+ private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+ private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
+
+ private final InternetDialogController mInternetDialogController;
+ private List<WifiEntry> mWifiEntries;
+ private int mWifiEntriesCount;
+
+ protected View mHolderView;
+ protected Context mContext;
+
+ public InternetAdapter(InternetDialogController controller) {
+ mInternetDialogController = controller;
+ }
+
+ @Override
+ public InternetViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+ int viewType) {
+ mContext = viewGroup.getContext();
+ mHolderView = LayoutInflater.from(mContext).inflate(R.layout.internet_list_item,
+ viewGroup, false);
+ return new InternetViewHolder(mHolderView, mInternetDialogController);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull InternetViewHolder viewHolder, int position) {
+ if (mWifiEntries == null || position >= mWifiEntriesCount) {
+ return;
+ }
+ viewHolder.onBind(mWifiEntries.get(position));
+ }
+
+ /**
+ * Updates the Wi-Fi networks.
+ *
+ * @param wifiEntries the updated Wi-Fi entries.
+ * @param wifiEntriesCount the total number of Wi-Fi entries.
+ */
+ public void setWifiEntries(@Nullable List<WifiEntry> wifiEntries, int wifiEntriesCount) {
+ mWifiEntries = wifiEntries;
+ mWifiEntriesCount = wifiEntriesCount;
+ }
+
+ /**
+ * Gets the total number of Wi-Fi networks.
+ *
+ * @return The total number of Wi-Fi entries.
+ */
+ @Override
+ public int getItemCount() {
+ return mWifiEntriesCount;
+ }
+
+ /**
+ * ViewHolder for binding Wi-Fi view.
+ */
+ static class InternetViewHolder extends RecyclerView.ViewHolder {
+
+ final LinearLayout mContainerLayout;
+ final LinearLayout mWifiListLayout;
+ final LinearLayout mWifiNetworkLayout;
+ final ImageView mWifiIcon;
+ final TextView mWifiTitleText;
+ final TextView mWifiSummaryText;
+ final ImageView mWifiEndIcon;
+ final Context mContext;
+ final InternetDialogController mInternetDialogController;
+
+ @VisibleForTesting
+ protected WifiUtils.InternetIconInjector mWifiIconInjector;
+
+ InternetViewHolder(View view, InternetDialogController internetDialogController) {
+ super(view);
+ mContext = view.getContext();
+ mInternetDialogController = internetDialogController;
+ mContainerLayout = view.requireViewById(R.id.internet_container);
+ mWifiListLayout = view.requireViewById(R.id.wifi_list);
+ mWifiNetworkLayout = view.requireViewById(R.id.wifi_network_layout);
+ mWifiIcon = view.requireViewById(R.id.wifi_icon);
+ mWifiTitleText = view.requireViewById(R.id.wifi_title);
+ mWifiSummaryText = view.requireViewById(R.id.wifi_summary);
+ mWifiEndIcon = view.requireViewById(R.id.wifi_end_icon);
+ mWifiIconInjector = mInternetDialogController.getWifiIconInjector();
+ }
+
+ void onBind(@NonNull WifiEntry wifiEntry) {
+ mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry));
+ setWifiNetworkLayout(wifiEntry.getTitle(),
+ Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY));
+
+ final int connectedState = wifiEntry.getConnectedState();
+ final int security = wifiEntry.getSecurity();
+ updateEndIcon(connectedState, security);
+
+ if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ mWifiListLayout.setOnClickListener(
+ v -> mInternetDialogController.launchWifiNetworkDetailsSetting(
+ wifiEntry.getKey()));
+ return;
+ }
+ mWifiListLayout.setOnClickListener(v -> {
+ if (wifiEntry.shouldEditBeforeConnect()) {
+ final Intent intent = new Intent(ACTION_WIFI_DIALOG);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey());
+ intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false);
+ mContext.startActivity(intent);
+ }
+ mInternetDialogController.connect(wifiEntry);
+ });
+ }
+
+ void setWifiNetworkLayout(CharSequence title, CharSequence summary) {
+ mWifiTitleText.setText(title);
+ if (TextUtils.isEmpty(summary)) {
+ mWifiSummaryText.setVisibility(View.GONE);
+ return;
+ }
+ mWifiSummaryText.setVisibility(View.VISIBLE);
+ mWifiSummaryText.setText(summary);
+ }
+
+ Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) {
+ if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+ return null;
+ }
+ final Drawable drawable = mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(),
+ wifiEntry.getLevel());
+ if (drawable == null) {
+ return null;
+ }
+ drawable.setTint(
+ Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorTertiary));
+ final AtomicReference<Drawable> shared = new AtomicReference<>();
+ shared.set(drawable);
+ return shared.get();
+ }
+
+ void updateEndIcon(int connectedState, int security) {
+ Drawable drawable = null;
+ if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ drawable = mContext.getDrawable(R.drawable.ic_settings_24dp);
+ } else if (security != WifiEntry.SECURITY_NONE && security != WifiEntry.SECURITY_OWE) {
+ drawable = mContext.getDrawable(R.drawable.ic_friction_lock_closed);
+ }
+ if (drawable == null) {
+ mWifiEndIcon.setVisibility(View.GONE);
+ return;
+ }
+ mWifiEndIcon.setVisibility(View.VISIBLE);
+ mWifiEndIcon.setImageDrawable(drawable);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
new file mode 100644
index 0000000..dc54e1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2021 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.qs.tiles.dialog;
+
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+
+import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.Html;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.List;
+
+/**
+ * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks.
+ */
+@SysUISingleton
+public class InternetDialog extends SystemUIDialog implements
+ InternetDialogController.InternetDialogCallback, Window.Callback {
+ private static final String TAG = "InternetDialog";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final long PROGRESS_DELAY_MS = 2000L;
+
+ private final Handler mHandler;
+ private final LinearLayoutManager mLayoutManager;
+
+ @VisibleForTesting
+ protected InternetAdapter mAdapter;
+ @VisibleForTesting
+ protected WifiManager mWifiManager;
+ @VisibleForTesting
+ protected View mDialogView;
+ @VisibleForTesting
+ protected boolean mCanConfigWifi;
+
+ private InternetDialogFactory mInternetDialogFactory;
+ private SubscriptionManager mSubscriptionManager;
+ private TelephonyManager mTelephonyManager;
+ private AlertDialog mAlertDialog;
+ private UiEventLogger mUiEventLogger;
+ private Context mContext;
+ private InternetDialogController mInternetDialogController;
+ private TextView mInternetDialogTitle;
+ private TextView mInternetDialogSubTitle;
+ private View mDivider;
+ private ProgressBar mProgressBar;
+ private LinearLayout mInternetDialogLayout;
+ private LinearLayout mConnectedWifListLayout;
+ private LinearLayout mMobileNetworkLayout;
+ private LinearLayout mTurnWifiOnLayout;
+ private LinearLayout mEthernetLayout;
+ private TextView mWifiToggleTitleText;
+ private LinearLayout mSeeAllLayout;
+ private RecyclerView mWifiRecyclerView;
+ private ImageView mConnectedWifiIcon;
+ private ImageView mWifiSettingsIcon;
+ private TextView mConnectedWifiTitleText;
+ private TextView mConnectedWifiSummaryText;
+ private ImageView mSignalIcon;
+ private TextView mMobileTitleText;
+ private TextView mMobileSummaryText;
+ private Switch mMobileDataToggle;
+ private Switch mWiFiToggle;
+ private FrameLayout mDoneLayout;
+ private Drawable mBackgroundOn;
+ private int mListMaxHeight;
+ private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private boolean mCanConfigMobileData;
+
+ // Wi-Fi entries
+ protected WifiEntry mConnectedWifiEntry;
+ protected int mWifiEntriesCount;
+
+ // Wi-Fi scanning progress bar
+ protected boolean mIsProgressBarVisible;
+ protected boolean mIsSearchingHidden;
+ protected final Runnable mHideProgressBarRunnable = () -> {
+ setProgressBarVisible(false);
+ };
+ protected Runnable mHideSearchingRunnable = () -> {
+ mIsSearchingHidden = true;
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ };
+
+ private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> {
+ // Set max height for list
+ if (mInternetDialogLayout.getHeight() > mListMaxHeight) {
+ ViewGroup.LayoutParams params = mInternetDialogLayout.getLayoutParams();
+ params.height = mListMaxHeight;
+ mInternetDialogLayout.setLayoutParams(params);
+ }
+ };
+
+ public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
+ InternetDialogController internetDialogController, boolean canConfigMobileData,
+ boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
+ @Main Handler handler) {
+ super(context, R.style.Theme_SystemUI_Dialog_Internet);
+ if (DEBUG) {
+ Log.d(TAG, "Init InternetDialog");
+ }
+ mContext = context;
+ mHandler = handler;
+ mInternetDialogFactory = internetDialogFactory;
+ mInternetDialogController = internetDialogController;
+ mSubscriptionManager = mInternetDialogController.getSubscriptionManager();
+ mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId();
+ mTelephonyManager = mInternetDialogController.getTelephonyManager();
+ mWifiManager = mInternetDialogController.getWifiManager();
+ mCanConfigMobileData = canConfigMobileData;
+ mCanConfigWifi = canConfigWifi;
+
+ mLayoutManager = new LinearLayoutManager(mContext) {
+ @Override
+ public boolean canScrollVertically() {
+ return false;
+ }
+ };
+ mListMaxHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.internet_dialog_list_max_height);
+ mUiEventLogger = uiEventLogger;
+ mAdapter = new InternetAdapter(mInternetDialogController);
+ if (!aboveStatusBar) {
+ getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) {
+ Log.d(TAG, "onCreate");
+ }
+ mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW);
+ mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog,
+ null);
+ final Window window = getWindow();
+ final WindowManager.LayoutParams layoutParams = window.getAttributes();
+ layoutParams.gravity = Gravity.BOTTOM;
+ // Move down the dialog to overlay the navigation bar.
+ layoutParams.setFitInsetsTypes(
+ layoutParams.getFitInsetsTypes() & ~WindowInsets.Type.navigationBars());
+ layoutParams.setFitInsetsSides(WindowInsets.Side.all());
+ layoutParams.setFitInsetsIgnoringVisibility(true);
+ window.setAttributes(layoutParams);
+ window.setContentView(mDialogView);
+ //Only fix the width for large screen or tablet.
+ window.setLayout(mContext.getResources().getDimensionPixelSize(
+ R.dimen.internet_dialog_list_max_width), ViewGroup.LayoutParams.WRAP_CONTENT);
+ window.setWindowAnimations(R.style.Animation_InternetDialog);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.addFlags(FLAG_LAYOUT_NO_LIMITS);
+
+ mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog);
+ mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
+ mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+ mDivider = mDialogView.requireViewById(R.id.divider);
+ mProgressBar = mDialogView.requireViewById(R.id.wifi_searching_progress);
+ mEthernetLayout = mDialogView.requireViewById(R.id.ethernet_layout);
+ mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+ mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title);
+ mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout);
+ mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon);
+ mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title);
+ mConnectedWifiSummaryText = mDialogView.requireViewById(R.id.wifi_connected_summary);
+ mWifiSettingsIcon = mDialogView.requireViewById(R.id.wifi_settings_icon);
+ mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout);
+ mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout);
+ mDoneLayout = mDialogView.requireViewById(R.id.done_layout);
+ mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
+ mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
+ mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
+ mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
+ mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
+ mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
+ mInternetDialogLayout.getViewTreeObserver().addOnGlobalLayoutListener(
+ mInternetListLayoutListener);
+ mInternetDialogTitle.setText(getDialogTitleText());
+ mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+
+ setOnClickListener();
+ mTurnWifiOnLayout.setBackground(null);
+ mWifiRecyclerView.setLayoutManager(mLayoutManager);
+ mWifiRecyclerView.setAdapter(mAdapter);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (DEBUG) {
+ Log.d(TAG, "onStart");
+ }
+ mInternetDialogController.onStart(this, mCanConfigWifi);
+ if (!mCanConfigWifi) {
+ hideWifiViews();
+ }
+ }
+
+ @VisibleForTesting
+ void hideWifiViews() {
+ setProgressBarVisible(false);
+ mTurnWifiOnLayout.setVisibility(View.GONE);
+ mConnectedWifListLayout.setVisibility(View.GONE);
+ mWifiRecyclerView.setVisibility(View.GONE);
+ mSeeAllLayout.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (DEBUG) {
+ Log.d(TAG, "onStop");
+ }
+ mHandler.removeCallbacks(mHideProgressBarRunnable);
+ mHandler.removeCallbacks(mHideSearchingRunnable);
+ mMobileNetworkLayout.setOnClickListener(null);
+ mMobileDataToggle.setOnCheckedChangeListener(null);
+ mConnectedWifListLayout.setOnClickListener(null);
+ mSeeAllLayout.setOnClickListener(null);
+ mWiFiToggle.setOnCheckedChangeListener(null);
+ mDoneLayout.setOnClickListener(null);
+ mInternetDialogController.onStop();
+ mInternetDialogFactory.destroyDialog();
+ }
+
+ @Override
+ public void dismissDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "dismissDialog");
+ }
+ mInternetDialogFactory.destroyDialog();
+ dismiss();
+ }
+
+ void updateDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "updateDialog");
+ }
+ if (mInternetDialogController.isAirplaneModeEnabled()) {
+ mInternetDialogSubTitle.setVisibility(View.GONE);
+ } else {
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ }
+ updateEthernet();
+ setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()
+ || mInternetDialogController.isCarrierNetworkActive());
+
+ if (!mCanConfigWifi) {
+ return;
+ }
+
+ showProgressBar();
+ final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
+ final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+ updateWifiToggle(isWifiEnabled, isDeviceLocked);
+ updateConnectedWifi(isWifiEnabled, isDeviceLocked);
+
+ final int visibility = (isDeviceLocked || !isWifiEnabled || mWifiEntriesCount <= 0)
+ ? View.GONE : View.VISIBLE;
+ mWifiRecyclerView.setVisibility(visibility);
+ mSeeAllLayout.setVisibility(visibility);
+ }
+
+ private void setOnClickListener() {
+ mMobileNetworkLayout.setOnClickListener(v -> {
+ if (mInternetDialogController.isMobileDataEnabled()
+ && !mInternetDialogController.isDeviceLocked()) {
+ if (!mInternetDialogController.activeNetworkIsCellular()) {
+ mInternetDialogController.connectCarrierNetwork();
+ }
+ }
+ });
+ mMobileDataToggle.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ if (!isChecked && shouldShowMobileDialog()) {
+ showTurnOffMobileDialog();
+ } else if (!shouldShowMobileDialog()) {
+ mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
+ isChecked, false);
+ }
+ });
+ mConnectedWifListLayout.setOnClickListener(v -> onClickConnectedWifi());
+ mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton());
+ mWiFiToggle.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ buttonView.setChecked(isChecked);
+ mWifiManager.setWifiEnabled(isChecked);
+ });
+ mDoneLayout.setOnClickListener(v -> dismiss());
+ }
+
+ @MainThread
+ private void updateEthernet() {
+ mEthernetLayout.setVisibility(
+ mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE);
+ }
+
+ private void setMobileDataLayout(boolean isCarrierNetworkConnected) {
+ if (mInternetDialogController.isAirplaneModeEnabled()
+ || !mInternetDialogController.hasCarrier()) {
+ mMobileNetworkLayout.setVisibility(View.GONE);
+ } else {
+ mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
+ mMobileNetworkLayout.setVisibility(View.VISIBLE);
+ mMobileTitleText.setText(getMobileNetworkTitle());
+ if (!TextUtils.isEmpty(getMobileNetworkSummary())) {
+ mMobileSummaryText.setText(
+ Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY));
+ mMobileSummaryText.setVisibility(View.VISIBLE);
+ } else {
+ mMobileSummaryText.setVisibility(View.GONE);
+ }
+ mSignalIcon.setImageDrawable(getSignalStrengthDrawable());
+ mMobileTitleText.setTextAppearance(isCarrierNetworkConnected
+ ? R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+ mMobileSummaryText.setTextAppearance(isCarrierNetworkConnected
+ ? R.style.TextAppearance_InternetDialog_Secondary_Active
+ : R.style.TextAppearance_InternetDialog_Secondary);
+ mMobileNetworkLayout.setBackground(isCarrierNetworkConnected ? mBackgroundOn : null);
+
+ mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ @MainThread
+ private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
+ mWiFiToggle.setChecked(isWifiEnabled);
+ if (isDeviceLocked) {
+ mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null)
+ ? R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+ }
+ mTurnWifiOnLayout.setBackground(
+ (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
+ }
+
+ @MainThread
+ private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
+ if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
+ mConnectedWifListLayout.setVisibility(View.GONE);
+ return;
+ }
+ mConnectedWifListLayout.setVisibility(View.VISIBLE);
+ mConnectedWifiTitleText.setText(mConnectedWifiEntry.getTitle());
+ mConnectedWifiSummaryText.setText(mConnectedWifiEntry.getSummary(false));
+ mConnectedWifiIcon.setImageDrawable(
+ mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry));
+ mWifiSettingsIcon.setColorFilter(
+ mContext.getColor(R.color.connected_network_primary_color));
+ }
+
+ void onClickConnectedWifi() {
+ if (mConnectedWifiEntry == null) {
+ return;
+ }
+ mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey());
+ }
+
+ void onClickSeeMoreButton() {
+ mInternetDialogController.launchNetworkSetting();
+ }
+
+ CharSequence getDialogTitleText() {
+ return mInternetDialogController.getDialogTitleText();
+ }
+
+ CharSequence getSubtitleText() {
+ return mInternetDialogController.getSubtitleText(
+ mIsProgressBarVisible && !mIsSearchingHidden);
+ }
+
+ private Drawable getSignalStrengthDrawable() {
+ return mInternetDialogController.getSignalStrengthDrawable();
+ }
+
+ CharSequence getMobileNetworkTitle() {
+ return mInternetDialogController.getMobileNetworkTitle();
+ }
+
+ String getMobileNetworkSummary() {
+ return mInternetDialogController.getMobileNetworkSummary();
+ }
+
+ protected void showProgressBar() {
+ if (mWifiManager == null || !mWifiManager.isWifiEnabled()
+ || mInternetDialogController.isDeviceLocked()) {
+ setProgressBarVisible(false);
+ return;
+ }
+ setProgressBarVisible(true);
+ if (mConnectedWifiEntry != null || mWifiEntriesCount > 0) {
+ mHandler.postDelayed(mHideProgressBarRunnable, PROGRESS_DELAY_MS);
+ } else if (!mIsSearchingHidden) {
+ mHandler.postDelayed(mHideSearchingRunnable, PROGRESS_DELAY_MS);
+ }
+ }
+
+ private void setProgressBarVisible(boolean visible) {
+ if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null
+ && mAdapter.mHolderView.isAttachedToWindow()) {
+ mIsProgressBarVisible = true;
+ }
+ mIsProgressBarVisible = visible;
+ mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
+ mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ }
+
+ private boolean shouldShowMobileDialog() {
+ boolean flag = Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA,
+ false);
+ if (mInternetDialogController.isMobileDataEnabled() && !flag) {
+ return true;
+ }
+ return false;
+ }
+
+ private void showTurnOffMobileDialog() {
+ CharSequence carrierName = getMobileNetworkTitle();
+ boolean isInService = mInternetDialogController.isVoiceStateInService();
+ if (TextUtils.isEmpty(carrierName) || !isInService) {
+ carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
+ }
+ mAlertDialog = new Builder(mContext)
+ .setTitle(R.string.mobile_data_disable_title)
+ .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
+ .setNegativeButton(android.R.string.cancel, (d, w) -> {
+ mMobileDataToggle.setChecked(true);
+ })
+ .setPositiveButton(
+ com.android.internal.R.string.alert_windows_notification_turn_off_action,
+ (d, w) -> {
+ mInternetDialogController.setMobileDataEnabled(mContext,
+ mDefaultDataSubId, false, false);
+ mMobileDataToggle.setChecked(false);
+ Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
+ })
+ .create();
+ mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true));
+ mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
+ SystemUIDialog.registerDismissListener(mAlertDialog);
+ SystemUIDialog.setWindowOnTop(mAlertDialog);
+ mAlertDialog.show();
+ }
+
+ @Override
+ public void onRefreshCarrierInfo() {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSimStateChanged() {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onLost(Network network) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSubscriptionsChanged(int defaultDataSubId) {
+ mDefaultDataSubId = defaultDataSubId;
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
+ @Nullable WifiEntry connectedEntry) {
+ mConnectedWifiEntry = connectedEntry;
+ mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+ mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
+ mHandler.post(() -> {
+ mAdapter.notifyDataSetChanged();
+ updateDialog();
+ });
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (mAlertDialog != null && !mAlertDialog.isShowing()) {
+ if (!hasFocus && isShowing()) {
+ dismiss();
+ }
+ }
+ }
+
+ public enum InternetDialogEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The Internet dialog became visible on the screen.")
+ INTERNET_DIALOG_SHOW(843);
+
+ private final int mId;
+
+ InternetDialogEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
new file mode 100644
index 0000000..aaba5ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -0,0 +1,1072 @@
+/*
+ * Copyright (C) 2021 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.qs.tiles.dialog;
+
+import static com.android.settingslib.mobile.MobileMappings.getIconKey;
+import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.DeviceInfoUtils;
+import com.android.settingslib.SignalIcon;
+import com.android.settingslib.Utils;
+import com.android.settingslib.graph.SignalDrawable;
+import com.android.settingslib.mobile.MobileMappings;
+import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.settingslib.net.SignalStrengthUtil;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
+import com.android.systemui.toast.SystemUIToast;
+import com.android.systemui.toast.ToastFactory;
+import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.wifitrackerlib.MergedCarrierEntry;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+public class InternetDialogController implements WifiEntry.DisconnectCallback,
+ NetworkController.AccessPointController.AccessPointCallback {
+
+ private static final String TAG = "InternetDialogController";
+ private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+ "android.settings.NETWORK_PROVIDER_SETTINGS";
+ private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+ public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
+ public static final int NO_CELL_DATA_TYPE_ICON = 0;
+ private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
+ private static final int SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT =
+ R.string.tap_a_network_to_connect;
+ private static final int SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS =
+ R.string.unlock_to_view_networks;
+ private static final int SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS =
+ R.string.wifi_empty_list_wifi_on;
+ private static final int SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE =
+ R.string.non_carrier_network_unavailable;
+ private static final int SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE =
+ R.string.all_network_unavailable;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final int MAX_WIFI_ENTRY_COUNT = 4;
+
+ private WifiManager mWifiManager;
+ private Context mContext;
+ private SubscriptionManager mSubscriptionManager;
+ private TelephonyManager mTelephonyManager;
+ private ConnectivityManager mConnectivityManager;
+ private CarrierConfigTracker mCarrierConfigTracker;
+ private TelephonyDisplayInfo mTelephonyDisplayInfo =
+ new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+ private Handler mHandler;
+ private Handler mWorkerHandler;
+ private MobileMappings.Config mConfig = null;
+ private Executor mExecutor;
+ private AccessPointController mAccessPointController;
+ private IntentFilter mConnectionStateFilter;
+ private InternetDialogCallback mCallback;
+ private WifiEntry mConnectedEntry;
+ private int mWifiEntriesCount;
+ private UiEventLogger mUiEventLogger;
+ private BroadcastDispatcher mBroadcastDispatcher;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private GlobalSettings mGlobalSettings;
+ private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
+ private WindowManager mWindowManager;
+ private ToastFactory mToastFactory;
+
+ @VisibleForTesting
+ static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
+ @VisibleForTesting
+ static final float TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f;
+ @VisibleForTesting
+ static final long SHORT_DURATION_TIMEOUT = 4000;
+ @VisibleForTesting
+ protected ActivityStarter mActivityStarter;
+ @VisibleForTesting
+ protected SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
+ @VisibleForTesting
+ protected InternetTelephonyCallback mInternetTelephonyCallback;
+ @VisibleForTesting
+ protected WifiUtils.InternetIconInjector mWifiIconInjector;
+ @VisibleForTesting
+ protected boolean mCanConfigWifi;
+ @VisibleForTesting
+ protected KeyguardStateController mKeyguardStateController;
+ @VisibleForTesting
+ protected boolean mHasEthernet = false;
+
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ mCallback.onRefreshCarrierInfo();
+ }
+
+ @Override
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ mCallback.onSimStateChanged();
+ }
+ };
+
+ protected List<SubscriptionInfo> getSubscriptionInfo() {
+ return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+ }
+
+ @Inject
+ public InternetDialogController(@NonNull Context context, UiEventLogger uiEventLogger,
+ ActivityStarter starter, AccessPointController accessPointController,
+ SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
+ @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+ @Main Handler handler, @Main Executor mainExecutor,
+ BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
+ WindowManager windowManager, ToastFactory toastFactory,
+ @Background Handler workerHandler,
+ CarrierConfigTracker carrierConfigTracker) {
+ if (DEBUG) {
+ Log.d(TAG, "Init InternetDialogController");
+ }
+ mHandler = handler;
+ mWorkerHandler = workerHandler;
+ mExecutor = mainExecutor;
+ mContext = context;
+ mGlobalSettings = globalSettings;
+ mWifiManager = wifiManager;
+ mTelephonyManager = telephonyManager;
+ mConnectivityManager = connectivityManager;
+ mSubscriptionManager = subscriptionManager;
+ mCarrierConfigTracker = carrierConfigTracker;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
+ mConnectionStateFilter = new IntentFilter();
+ mConnectionStateFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+ mConnectionStateFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+ mUiEventLogger = uiEventLogger;
+ mActivityStarter = starter;
+ mAccessPointController = accessPointController;
+ mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
+ mConnectivityManagerNetworkCallback = new DataConnectivityListener();
+ mWindowManager = windowManager;
+ mToastFactory = toastFactory;
+ }
+
+ void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
+ if (DEBUG) {
+ Log.d(TAG, "onStart");
+ }
+ mCallback = callback;
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+ mAccessPointController.addAccessPointCallback(this);
+ mBroadcastDispatcher.registerReceiver(mConnectionStateReceiver, mConnectionStateFilter,
+ mExecutor);
+ // Listen the subscription changes
+ mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
+ mOnSubscriptionsChangedListener);
+ mDefaultDataSubId = getDefaultDataSubscriptionId();
+ if (DEBUG) {
+ Log.d(TAG, "Init, SubId: " + mDefaultDataSubId);
+ }
+ mConfig = MobileMappings.Config.readConfig(mContext);
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mInternetTelephonyCallback = new InternetTelephonyCallback();
+ mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+ // Listen the connectivity changes
+ mConnectivityManager.registerDefaultNetworkCallback(mConnectivityManagerNetworkCallback);
+ mCanConfigWifi = canConfigWifi;
+ scanWifiAccessPoints();
+ }
+
+ void onStop() {
+ if (DEBUG) {
+ Log.d(TAG, "onStop");
+ }
+ mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver);
+ mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ mSubscriptionManager.removeOnSubscriptionsChangedListener(
+ mOnSubscriptionsChangedListener);
+ mAccessPointController.removeAccessPointCallback(this);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+ mConnectivityManager.unregisterNetworkCallback(mConnectivityManagerNetworkCallback);
+ }
+
+ @VisibleForTesting
+ boolean isAirplaneModeEnabled() {
+ return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+ }
+
+ @VisibleForTesting
+ protected int getDefaultDataSubscriptionId() {
+ return mSubscriptionManager.getDefaultDataSubscriptionId();
+ }
+
+ @VisibleForTesting
+ protected Intent getSettingsIntent() {
+ return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ protected Intent getWifiDetailsSettingsIntent(String key) {
+ if (TextUtils.isEmpty(key)) {
+ if (DEBUG) {
+ Log.d(TAG, "connected entry's key is empty");
+ }
+ return null;
+ }
+ return WifiUtils.getWifiDetailsSettingsIntent(key);
+ }
+
+ CharSequence getDialogTitleText() {
+ if (isAirplaneModeEnabled()) {
+ return mContext.getText(R.string.airplane_mode);
+ }
+ return mContext.getText(R.string.quick_settings_internet_label);
+ }
+
+ CharSequence getSubtitleText(boolean isProgressBarVisible) {
+ if (isAirplaneModeEnabled()) {
+ return null;
+ }
+
+ if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) {
+ // When the airplane mode is off and Wi-Fi is disabled.
+ // Sub-Title: Wi-Fi is off
+ if (DEBUG) {
+ Log.d(TAG, "Airplane mode off + Wi-Fi off.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF);
+ }
+
+ if (isDeviceLocked()) {
+ // When the device is locked.
+ // Sub-Title: Unlock to view networks
+ if (DEBUG) {
+ Log.d(TAG, "The device is locked.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS);
+ }
+
+ if (mConnectedEntry != null || mWifiEntriesCount > 0) {
+ return mCanConfigWifi ? mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT) : null;
+ }
+
+ if (mCanConfigWifi && isProgressBarVisible) {
+ // When the Wi-Fi scan result callback is received
+ // Sub-Title: Searching for networks...
+ return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS);
+ }
+
+ // Sub-Title:
+ // show non_carrier_network_unavailable
+ // - while Wi-Fi on + no Wi-Fi item
+ // - while Wi-Fi on + no Wi-Fi item + mobile data off
+ // show all_network_unavailable:
+ // - while Wi-Fi on + no Wi-Fi item + no carrier item
+ // - while Wi-Fi on + no Wi-Fi item + service is out of service
+ // - while Wi-Fi on + no Wi-Fi item + mobile data on + no carrier data.
+ if (DEBUG) {
+ Log.d(TAG, "No Wi-Fi item.");
+ }
+ if (!hasCarrier() || (!isVoiceStateInService() && !isDataStateInService())) {
+ if (DEBUG) {
+ Log.d(TAG, "No carrier or service is out of service.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (mCanConfigWifi && !isMobileDataEnabled()) {
+ if (DEBUG) {
+ Log.d(TAG, "Mobile data off");
+ }
+ return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (!activeNetworkIsCellular()) {
+ if (DEBUG) {
+ Log.d(TAG, "No carrier data.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (mCanConfigWifi) {
+ return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+ }
+ return null;
+ }
+
+ Drawable getInternetWifiDrawable(@NonNull WifiEntry wifiEntry) {
+ if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+ return null;
+ }
+ final Drawable drawable =
+ mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(), wifiEntry.getLevel());
+ if (drawable == null) {
+ return null;
+ }
+ drawable.setTint(mContext.getColor(R.color.connected_network_primary_color));
+ return drawable;
+ }
+
+ Drawable getSignalStrengthDrawable() {
+ Drawable drawable = mContext.getDrawable(
+ R.drawable.ic_signal_strength_zero_bar_no_internet);
+ try {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null");
+ }
+ return drawable;
+ }
+
+ if (isDataStateInService() || isVoiceStateInService()) {
+ AtomicReference<Drawable> shared = new AtomicReference<>();
+ shared.set(getSignalStrengthDrawableWithLevel());
+ drawable = shared.get();
+ }
+
+ int tintColor = Utils.getColorAttrDefaultColor(mContext,
+ android.R.attr.textColorTertiary);
+ if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
+ tintColor = mContext.getColor(R.color.connected_network_primary_color);
+ }
+ drawable.setTint(tintColor);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return drawable;
+ }
+
+ /**
+ * To get the signal bar icon with level.
+ *
+ * @return The Drawable which is a signal bar icon with level.
+ */
+ Drawable getSignalStrengthDrawableWithLevel() {
+ final SignalStrength strength = mTelephonyManager.getSignalStrength();
+ int level = (strength == null) ? 0 : strength.getLevel();
+ int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+ if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) {
+ level += 1;
+ numLevels += 1;
+ }
+ return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
+ !isMobileDataEnabled());
+ }
+
+ Drawable getSignalStrengthIcon(Context context, int level, int numLevels,
+ int iconType, boolean cutOut) {
+ Log.d(TAG, "getSignalStrengthIcon");
+ final SignalDrawable signalDrawable = new SignalDrawable(context);
+ signalDrawable.setLevel(
+ SignalDrawable.getState(level, numLevels, cutOut));
+
+ // Make the network type drawable
+ final Drawable networkDrawable =
+ iconType == NO_CELL_DATA_TYPE_ICON
+ ? EMPTY_DRAWABLE
+ : context.getResources().getDrawable(iconType, context.getTheme());
+
+ // Overlay the two drawables
+ final Drawable[] layers = {networkDrawable, signalDrawable};
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size);
+
+ final LayerDrawable icons = new LayerDrawable(layers);
+ // Set the network type icon at the top left
+ icons.setLayerGravity(0 /* index of networkDrawable */, Gravity.TOP | Gravity.LEFT);
+ // Set the signal strength icon at the bottom right
+ icons.setLayerGravity(1 /* index of SignalDrawable */, Gravity.BOTTOM | Gravity.RIGHT);
+ icons.setLayerSize(1 /* index of SignalDrawable */, iconSize, iconSize);
+ icons.setTintList(Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+ return icons;
+ }
+
+ private boolean shouldInflateSignalStrength(int subId) {
+ return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId);
+ }
+
+ private CharSequence getUniqueSubscriptionDisplayName(int subscriptionId, Context context) {
+ final Map<Integer, CharSequence> displayNames = getUniqueSubscriptionDisplayNames(context);
+ return displayNames.getOrDefault(subscriptionId, "");
+ }
+
+ private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) {
+ class DisplayInfo {
+ public SubscriptionInfo subscriptionInfo;
+ public CharSequence originalName;
+ public CharSequence uniqueName;
+ }
+
+ // Map of SubscriptionId to DisplayName
+ final Supplier<Stream<DisplayInfo>> originalInfos =
+ () -> getSubscriptionInfo()
+ .stream()
+ .filter(i -> {
+ // Filter out null values.
+ return (i != null && i.getDisplayName() != null);
+ })
+ .map(i -> {
+ DisplayInfo info = new DisplayInfo();
+ info.subscriptionInfo = i;
+ info.originalName = i.getDisplayName().toString().trim();
+ return info;
+ });
+
+ // A Unique set of display names
+ Set<CharSequence> uniqueNames = new HashSet<>();
+ // Return the set of duplicate names
+ final Set<CharSequence> duplicateOriginalNames = originalInfos.get()
+ .filter(info -> !uniqueNames.add(info.originalName))
+ .map(info -> info.originalName)
+ .collect(Collectors.toSet());
+
+ // If a display name is duplicate, append the final 4 digits of the phone number.
+ // Creates a mapping of Subscription id to original display name + phone number display name
+ final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> {
+ if (duplicateOriginalNames.contains(info.originalName)) {
+ // This may return null, if the user cannot view the phone number itself.
+ final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context,
+ info.subscriptionInfo);
+ String lastFourDigits = "";
+ if (phoneNumber != null) {
+ lastFourDigits = (phoneNumber.length() > 4)
+ ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber;
+ }
+
+ if (TextUtils.isEmpty(lastFourDigits)) {
+ info.uniqueName = info.originalName;
+ } else {
+ info.uniqueName = info.originalName + " " + lastFourDigits;
+ }
+
+ } else {
+ info.uniqueName = info.originalName;
+ }
+ return info;
+ });
+
+ // Check uniqueness a second time.
+ // We might not have had permission to view the phone numbers.
+ // There might also be multiple phone numbers whose last 4 digits the same.
+ uniqueNames.clear();
+ final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get()
+ .filter(info -> !uniqueNames.add(info.uniqueName))
+ .map(info -> info.uniqueName)
+ .collect(Collectors.toSet());
+
+ return uniqueInfos.get().map(info -> {
+ if (duplicatePhoneNames.contains(info.uniqueName)) {
+ info.uniqueName = info.originalName + " "
+ + info.subscriptionInfo.getSubscriptionId();
+ }
+ return info;
+ }).collect(Collectors.toMap(
+ info -> info.subscriptionInfo.getSubscriptionId(),
+ info -> info.uniqueName));
+ }
+
+ CharSequence getMobileNetworkTitle() {
+ return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext);
+ }
+
+ String getMobileNetworkSummary() {
+ String description = getNetworkTypeDescription(mContext, mConfig,
+ mTelephonyDisplayInfo, mDefaultDataSubId);
+ return getMobileSummary(mContext, description);
+ }
+
+ /**
+ * Get currently description of mobile network type.
+ */
+ private String getNetworkTypeDescription(Context context, MobileMappings.Config config,
+ TelephonyDisplayInfo telephonyDisplayInfo, int subId) {
+ String iconKey = getIconKey(telephonyDisplayInfo);
+
+ if (mapIconSets(config) == null || mapIconSets(config).get(iconKey) == null) {
+ if (DEBUG) {
+ Log.d(TAG, "The description of network type is empty.");
+ }
+ return "";
+ }
+
+ int resId = mapIconSets(config).get(iconKey).dataContentDescription;
+ if (isCarrierNetworkActive()) {
+ SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
+ TelephonyIcons.CARRIER_MERGED_WIFI;
+ resId = carrierMergedWifiIconGroup.dataContentDescription;
+ }
+
+ return resId != 0
+ ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : "";
+ }
+
+ private String getMobileSummary(Context context, String networkTypeDescription) {
+ if (!isMobileDataEnabled()) {
+ return context.getString(R.string.mobile_data_off_summary);
+ }
+ if (!isDataStateInService()) {
+ return context.getString(R.string.mobile_data_no_connection);
+ }
+ String summary = networkTypeDescription;
+ if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
+ summary = context.getString(R.string.preference_summary_default_combination,
+ context.getString(R.string.mobile_data_connection_active),
+ networkTypeDescription);
+ }
+ return summary;
+ }
+
+ void launchNetworkSetting() {
+ mCallback.dismissDialog();
+ mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
+ }
+
+ void launchWifiNetworkDetailsSetting(String key) {
+ Intent intent = getWifiDetailsSettingsIntent(key);
+ if (intent != null) {
+ mCallback.dismissDialog();
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+ }
+
+ void connectCarrierNetwork() {
+ final MergedCarrierEntry mergedCarrierEntry =
+ mAccessPointController.getMergedCarrierEntry();
+ if (mergedCarrierEntry != null && mergedCarrierEntry.canConnect()) {
+ mergedCarrierEntry.connect(null /* ConnectCallback */, false);
+ makeOverlayToast(R.string.wifi_wont_autoconnect_for_now);
+ }
+ }
+
+ boolean isCarrierNetworkActive() {
+ final MergedCarrierEntry mergedCarrierEntry =
+ mAccessPointController.getMergedCarrierEntry();
+ return mergedCarrierEntry != null && mergedCarrierEntry.isDefaultNetwork();
+ }
+
+ @WorkerThread
+ void setMergedCarrierWifiEnabledIfNeed(int subId, boolean enabled) {
+ // If the Carrier Provisions Wi-Fi Merged Networks enabled, do not set the merged carrier
+ // Wi-Fi state together.
+ if (mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(subId)) {
+ return;
+ }
+
+ final MergedCarrierEntry entry = mAccessPointController.getMergedCarrierEntry();
+ if (entry == null) {
+ if (DEBUG) {
+ Log.d(TAG, "MergedCarrierEntry is null, can not set the status.");
+ }
+ return;
+ }
+ entry.setEnabled(enabled);
+ }
+
+ WifiManager getWifiManager() {
+ return mWifiManager;
+ }
+
+ TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ SubscriptionManager getSubscriptionManager() {
+ return mSubscriptionManager;
+ }
+
+ /**
+ * @return whether there is the carrier item in the slice.
+ */
+ boolean hasCarrier() {
+ if (mSubscriptionManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
+ }
+ return false;
+ }
+
+ if (isAirplaneModeEnabled() || mTelephonyManager == null
+ || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return {@code true} if mobile data is enabled
+ */
+ boolean isMobileDataEnabled() {
+ if (mTelephonyManager == null || !mTelephonyManager.isDataEnabled()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set whether to enable data for {@code subId}, also whether to disable data for other
+ * subscription
+ */
+ void setMobileDataEnabled(Context context, int subId, boolean enabled,
+ boolean disableOtherSubscriptions) {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null, can not set mobile data.");
+ }
+ return;
+ }
+
+ if (mSubscriptionManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "SubscriptionManager is null, can not set mobile data.");
+ }
+ return;
+ }
+
+ mTelephonyManager.setDataEnabled(enabled);
+ if (disableOtherSubscriptions) {
+ final List<SubscriptionInfo> subInfoList =
+ mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (subInfoList != null) {
+ for (SubscriptionInfo subInfo : subInfoList) {
+ // We never disable mobile data for opportunistic subscriptions.
+ if (subInfo.getSubscriptionId() != subId && !subInfo.isOpportunistic()) {
+ context.getSystemService(TelephonyManager.class).createForSubscriptionId(
+ subInfo.getSubscriptionId()).setDataEnabled(false);
+ }
+ }
+ }
+ }
+ mWorkerHandler.post(() -> setMergedCarrierWifiEnabledIfNeed(subId, enabled));
+ }
+
+ boolean isDataStateInService() {
+ final ServiceState serviceState = mTelephonyManager.getServiceState();
+ NetworkRegistrationInfo regInfo =
+ (serviceState == null) ? null : serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ return (regInfo == null) ? false : regInfo.isRegistered();
+ }
+
+ boolean isVoiceStateInService() {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null, can not detect voice state.");
+ }
+ return false;
+ }
+
+ final ServiceState serviceState = mTelephonyManager.getServiceState();
+ return serviceState != null
+ && serviceState.getState() == serviceState.STATE_IN_SERVICE;
+ }
+
+ public boolean isDeviceLocked() {
+ return !mKeyguardStateController.isUnlocked();
+ }
+
+ boolean activeNetworkIsCellular() {
+ if (mConnectivityManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "ConnectivityManager is null, can not check active network.");
+ }
+ return false;
+ }
+
+ final Network activeNetwork = mConnectivityManager.getActiveNetwork();
+ if (activeNetwork == null) {
+ return false;
+ }
+ final NetworkCapabilities networkCapabilities =
+ mConnectivityManager.getNetworkCapabilities(activeNetwork);
+ if (networkCapabilities == null) {
+ return false;
+ }
+ return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
+ }
+
+ boolean connect(WifiEntry ap) {
+ if (ap == null) {
+ if (DEBUG) {
+ Log.d(TAG, "No Wi-Fi ap to connect.");
+ }
+ return false;
+ }
+
+ if (ap.getWifiConfiguration() != null) {
+ if (DEBUG) {
+ Log.d(TAG, "connect networkId=" + ap.getWifiConfiguration().networkId);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "connect to unsaved network " + ap.getTitle());
+ }
+ }
+ ap.connect(new WifiEntryConnectCallback(mActivityStarter, ap, this));
+ return false;
+ }
+
+ static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback {
+ final ActivityStarter mActivityStarter;
+ final WifiEntry mWifiEntry;
+ final InternetDialogController mInternetDialogController;
+
+ WifiEntryConnectCallback(ActivityStarter activityStarter, WifiEntry connectWifiEntry,
+ InternetDialogController internetDialogController) {
+ mActivityStarter = activityStarter;
+ mWifiEntry = connectWifiEntry;
+ mInternetDialogController = internetDialogController;
+ }
+
+ @Override
+ public void onConnectResult(@ConnectStatus int status) {
+ if (DEBUG) {
+ Log.d(TAG, "onConnectResult " + status);
+ }
+
+ if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
+ final Intent intent = new Intent("com.android.settings.WIFI_DIALOG")
+ .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
+ } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
+ mInternetDialogController.makeOverlayToast(R.string.wifi_failed_connect_message);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "connect failure reason=" + status);
+ }
+ }
+ }
+ }
+
+ private void scanWifiAccessPoints() {
+ if (mCanConfigWifi) {
+ mAccessPointController.scanForAccessPoints();
+ }
+ }
+
+ @Override
+ @WorkerThread
+ public void onAccessPointsChanged(List<WifiEntry> accessPoints) {
+ if (!mCanConfigWifi) {
+ return;
+ }
+
+ if (accessPoints == null || accessPoints.size() == 0) {
+ mConnectedEntry = null;
+ mWifiEntriesCount = 0;
+ if (mCallback != null) {
+ mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ }
+ return;
+ }
+
+ boolean hasConnectedWifi = false;
+ final int accessPointSize = accessPoints.size();
+ for (int i = 0; i < accessPointSize; i++) {
+ WifiEntry wifiEntry = accessPoints.get(i);
+ if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) {
+ mConnectedEntry = wifiEntry;
+ hasConnectedWifi = true;
+ break;
+ }
+ }
+ if (!hasConnectedWifi) {
+ mConnectedEntry = null;
+ }
+
+ int count = MAX_WIFI_ENTRY_COUNT;
+ if (mHasEthernet) {
+ count -= 1;
+ }
+ if (hasCarrier()) {
+ count -= 1;
+ }
+ if (hasConnectedWifi) {
+ count -= 1;
+ }
+ final List<WifiEntry> wifiEntries = accessPoints.stream()
+ .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork()
+ || !wifiEntry.hasInternetAccess()))
+ .limit(count)
+ .collect(Collectors.toList());
+ mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+
+ if (mCallback != null) {
+ mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
+ }
+ }
+
+ @Override
+ public void onSettingsActivityTriggered(Intent settingsIntent) {
+ }
+
+ @Override
+ public void onDisconnectResult(int status) {
+ }
+
+ private class InternetTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DisplayInfoListener,
+ TelephonyCallback.ServiceStateListener,
+ TelephonyCallback.SignalStrengthsListener {
+
+ @Override
+ public void onServiceStateChanged(@NonNull ServiceState serviceState) {
+ mCallback.onServiceStateChanged(serviceState);
+ }
+
+ @Override
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ mCallback.onDataConnectionStateChanged(state, networkType);
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
+ mCallback.onSignalStrengthsChanged(signalStrength);
+ }
+
+ @Override
+ public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
+ mTelephonyDisplayInfo = telephonyDisplayInfo;
+ mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
+ }
+ }
+
+ private class InternetOnSubscriptionChangedListener
+ extends SubscriptionManager.OnSubscriptionsChangedListener {
+ InternetOnSubscriptionChangedListener() {
+ super();
+ }
+
+ @Override
+ public void onSubscriptionsChanged() {
+ updateListener();
+ }
+ }
+
+ private class DataConnectivityListener extends ConnectivityManager.NetworkCallback {
+ @Override
+ @WorkerThread
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities capabilities) {
+ mHasEthernet = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET);
+ if (mCanConfigWifi && (mHasEthernet || capabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_WIFI))) {
+ scanWifiAccessPoints();
+ }
+ // update UI
+ mCallback.onCapabilitiesChanged(network, capabilities);
+ }
+
+ @Override
+ @WorkerThread
+ public void onLost(@NonNull Network network) {
+ mHasEthernet = false;
+ mCallback.onLost(network);
+ }
+ }
+
+ /**
+ * Return {@code true} If the Ethernet exists
+ */
+ @MainThread
+ public boolean hasEthernet() {
+ return mHasEthernet;
+ }
+
+ private final BroadcastReceiver mConnectionStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
+ if (DEBUG) {
+ Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
+ }
+ mConfig = MobileMappings.Config.readConfig(context);
+ updateListener();
+ } else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
+ updateListener();
+ }
+ }
+ };
+
+ private void updateListener() {
+ int defaultDataSubId = getDefaultDataSubscriptionId();
+ if (mDefaultDataSubId == getDefaultDataSubscriptionId()) {
+ if (DEBUG) {
+ Log.d(TAG, "DDS: no change");
+ }
+ return;
+ }
+
+ mDefaultDataSubId = defaultDataSubId;
+ if (DEBUG) {
+ Log.d(TAG, "DDS: defaultDataSubId:" + mDefaultDataSubId);
+ }
+ if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) {
+ mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mTelephonyManager.registerTelephonyCallback(mHandler::post,
+ mInternetTelephonyCallback);
+ mCallback.onSubscriptionsChanged(mDefaultDataSubId);
+ }
+ }
+
+ public WifiUtils.InternetIconInjector getWifiIconInjector() {
+ return mWifiIconInjector;
+ }
+
+ interface InternetDialogCallback {
+
+ void onRefreshCarrierInfo();
+
+ void onSimStateChanged();
+
+ void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities);
+
+ void onLost(@NonNull Network network);
+
+ void onSubscriptionsChanged(int defaultDataSubId);
+
+ void onServiceStateChanged(ServiceState serviceState);
+
+ void onDataConnectionStateChanged(int state, int networkType);
+
+ void onSignalStrengthsChanged(SignalStrength signalStrength);
+
+ void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo);
+
+ void dismissDialog();
+
+ void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
+ @Nullable WifiEntry connectedEntry);
+ }
+
+ void makeOverlayToast(int stringId) {
+ final Resources res = mContext.getResources();
+
+ final SystemUIToast systemUIToast = mToastFactory.createToast(mContext,
+ res.getString(stringId), mContext.getPackageName(), UserHandle.myUserId(),
+ res.getConfiguration().orientation);
+ if (systemUIToast == null) {
+ return;
+ }
+
+ View toastView = systemUIToast.getView();
+
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.format = PixelFormat.TRANSLUCENT;
+ params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+ params.y = systemUIToast.getYOffset();
+
+ int absGravity = Gravity.getAbsoluteGravity(systemUIToast.getGravity(),
+ res.getConfiguration().getLayoutDirection());
+ params.gravity = absGravity;
+ if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+ params.horizontalWeight = TOAST_PARAMS_HORIZONTAL_WEIGHT;
+ }
+ if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+ params.verticalWeight = TOAST_PARAMS_VERTICAL_WEIGHT;
+ }
+
+ mWindowManager.addView(toastView, params);
+
+ Animator inAnimator = systemUIToast.getInAnimation();
+ if (inAnimator != null) {
+ inAnimator.start();
+ }
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Animator outAnimator = systemUIToast.getOutAnimation();
+ if (outAnimator != null) {
+ outAnimator.start();
+ outAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mWindowManager.removeViewImmediate(toastView);
+ }
+ });
+ }
+ }
+ }, SHORT_DURATION_TIMEOUT);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
new file mode 100644
index 0000000..11c6980
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 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.qs.tiles.dialog
+
+import android.content.Context
+import android.os.Handler
+import android.util.Log
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+
+private const val TAG = "InternetDialogFactory"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Factory to create [InternetDialog] objects.
+ */
+@SysUISingleton
+class InternetDialogFactory @Inject constructor(
+ @Main private val handler: Handler,
+ private val internetDialogController: InternetDialogController,
+ private val context: Context,
+ private val uiEventLogger: UiEventLogger
+) {
+ companion object {
+ var internetDialog: InternetDialog? = null
+ }
+
+ /** Creates a [InternetDialog]. */
+ fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) {
+ if (internetDialog != null) {
+ if (DEBUG) {
+ Log.d(TAG, "InternetDialog is showing, do not create it twice.")
+ }
+ return
+ } else {
+ internetDialog = InternetDialog(context, this, internetDialogController,
+ canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler)
+ internetDialog?.show()
+ }
+ }
+
+ fun destroyDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "destroyDialog")
+ }
+ internetDialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java
new file mode 100644
index 0000000..6aaba99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java
@@ -0,0 +1,14 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+public class InternetDialogUtil {
+
+ public static boolean isProviderModelEnabled(Context context) {
+ if (context == null) {
+ return false;
+ }
+ return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 26781f4..2133cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -29,8 +29,8 @@
import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
@@ -187,77 +187,63 @@
* @param refreshRate Desired refresh rate
* @return array with supported width, height, and refresh rate
*/
- private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate) {
- double maxScale = 0;
+ private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate)
+ throws IOException {
+ String videoType = MediaFormat.MIMETYPE_VIDEO_AVC;
- MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- MediaCodecInfo.VideoCapabilities maxInfo = null;
- for (MediaCodecInfo codec : codecList.getCodecInfos()) {
- String videoType = MediaFormat.MIMETYPE_VIDEO_AVC;
- String[] types = codec.getSupportedTypes();
- for (String t : types) {
- if (!t.equalsIgnoreCase(videoType)) {
- continue;
- }
- MediaCodecInfo.CodecCapabilities capabilities =
- codec.getCapabilitiesForType(videoType);
- if (capabilities != null && capabilities.getVideoCapabilities() != null) {
- MediaCodecInfo.VideoCapabilities vc = capabilities.getVideoCapabilities();
+ // Get max size from the decoder, to ensure recordings will be playable on device
+ MediaCodec decoder = MediaCodec.createDecoderByType(videoType);
+ MediaCodecInfo.VideoCapabilities vc = decoder.getCodecInfo()
+ .getCapabilitiesForType(videoType).getVideoCapabilities();
+ decoder.release();
- int width = vc.getSupportedWidths().getUpper();
- int height = vc.getSupportedHeights().getUpper();
+ // Check if we can support screen size as-is
+ int width = vc.getSupportedWidths().getUpper();
+ int height = vc.getSupportedHeights().getUpper();
- int screenWidthAligned = screenWidth;
- if (screenWidthAligned % vc.getWidthAlignment() != 0) {
- screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment());
- }
- int screenHeightAligned = screenHeight;
- if (screenHeightAligned % vc.getHeightAlignment() != 0) {
- screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment());
- }
+ int screenWidthAligned = screenWidth;
+ if (screenWidthAligned % vc.getWidthAlignment() != 0) {
+ screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment());
+ }
+ int screenHeightAligned = screenHeight;
+ if (screenHeightAligned % vc.getHeightAlignment() != 0) {
+ screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment());
+ }
- if (width >= screenWidthAligned && height >= screenHeightAligned
- && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) {
- // Desired size is supported, now get the rate
- int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned,
- screenHeightAligned).getUpper().intValue();
+ if (width >= screenWidthAligned && height >= screenHeightAligned
+ && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) {
+ // Desired size is supported, now get the rate
+ int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned,
+ screenHeightAligned).getUpper().intValue();
- if (maxRate < refreshRate) {
- refreshRate = maxRate;
- }
- Log.d(TAG, "Screen size supported at rate " + refreshRate);
- return new int[]{screenWidthAligned, screenHeightAligned, refreshRate};
- }
-
- // Otherwise, continue searching
- double scale = Math.min(((double) width / screenWidth),
- ((double) height / screenHeight));
- if (scale > maxScale) {
- maxScale = Math.min(1, scale);
- maxInfo = vc;
- }
- }
+ if (maxRate < refreshRate) {
+ refreshRate = maxRate;
}
+ Log.d(TAG, "Screen size supported at rate " + refreshRate);
+ return new int[]{screenWidthAligned, screenHeightAligned, refreshRate};
}
- // Resize for max supported size
- int scaledWidth = (int) (screenWidth * maxScale);
- int scaledHeight = (int) (screenHeight * maxScale);
- if (scaledWidth % maxInfo.getWidthAlignment() != 0) {
- scaledWidth -= (scaledWidth % maxInfo.getWidthAlignment());
+ // Otherwise, resize for max supported size
+ double scale = Math.min(((double) width / screenWidth),
+ ((double) height / screenHeight));
+
+ int scaledWidth = (int) (screenWidth * scale);
+ int scaledHeight = (int) (screenHeight * scale);
+ if (scaledWidth % vc.getWidthAlignment() != 0) {
+ scaledWidth -= (scaledWidth % vc.getWidthAlignment());
}
- if (scaledHeight % maxInfo.getHeightAlignment() != 0) {
- scaledHeight -= (scaledHeight % maxInfo.getHeightAlignment());
+ if (scaledHeight % vc.getHeightAlignment() != 0) {
+ scaledHeight -= (scaledHeight % vc.getHeightAlignment());
}
// Find max supported rate for size
- int maxRate = maxInfo.getSupportedFrameRatesFor(scaledWidth, scaledHeight)
+ int maxRate = vc.getSupportedFrameRatesFor(scaledWidth, scaledHeight)
.getUpper().intValue();
if (maxRate < refreshRate) {
refreshRate = maxRate;
}
- Log.d(TAG, "Resized by " + maxScale + ": " + scaledWidth + ", " + scaledHeight
+ Log.d(TAG, "Resized by " + scale + ": " + scaledWidth + ", " + scaledHeight
+ ", " + refreshRate);
return new int[]{scaledWidth, scaledHeight, refreshRate};
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 0eaef72..31d51f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -154,6 +154,7 @@
@Override
public void onStart() {
super.onStart();
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED);
if (mPreview.getDrawable() != null) {
// We already have an image, so no need to try to load again.
@@ -245,6 +246,8 @@
}
private void onCachedImageLoaded(ImageLoader.Result imageResult) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED);
+
BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
mPreview.setImageDrawable(drawable);
mPreview.setAlpha(1f);
@@ -282,6 +285,8 @@
finish();
}
if (isFinishing()) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED);
+
if (mScrollCaptureResponse != null) {
mScrollCaptureResponse.close();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 16872b0..8def475 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -33,6 +33,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.annotation.MainThread;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -261,6 +262,7 @@
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
+ private boolean mBlockAttach;
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
@@ -559,8 +561,8 @@
mScreenshotView.reset();
}
- mScreenshotView.updateOrientation(mWindowManager.getCurrentWindowMetrics()
- .getWindowInsets().getDisplayCutout());
+ mScreenshotView.updateOrientation(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
mScreenBitmap = screenshot;
@@ -594,9 +596,8 @@
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(this::requestScrollCapture, 150);
- mScreenshotView.updateDisplayCutoutMargins(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets()
- .getDisplayCutout());
+ mScreenshotView.updateInsets(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// screenshot animation calculations won't be valid anymore, so just end
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
mScreenshotAnimation.end();
@@ -660,7 +661,7 @@
+ mLastScrollCaptureResponse.getWindowTitle() + "]");
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mScreenshotView.showScrollChip(/* onClick */ () -> {
+ mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDefaultDisplay().getRealMetrics(displayMetrics);
Bitmap newScreenshot = captureScreenshot(
@@ -732,6 +733,7 @@
new ViewTreeObserver.OnWindowAttachListener() {
@Override
public void onWindowAttached() {
+ mBlockAttach = false;
decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
action.run();
}
@@ -748,14 +750,16 @@
mWindow.setContentView(contentView);
}
+ @MainThread
private void attachWindow() {
View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow()) {
+ if (decorView.isAttachedToWindow() || mBlockAttach) {
return;
}
if (DEBUG_WINDOW) {
Log.d(TAG, "attachWindow");
}
+ mBlockAttach = true;
mWindowManager.addView(decorView, mWindowLayoutParams);
decorView.requestApplyInsets();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 5cf0188..169b28c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -71,7 +71,19 @@
@UiEvent(doc = "User has shared a long screenshot")
SCREENSHOT_LONG_SCREENSHOT_SHARE(689),
@UiEvent(doc = "User has sent a long screenshot to the editor")
- SCREENSHOT_LONG_SCREENSHOT_EDIT(690);
+ SCREENSHOT_LONG_SCREENSHOT_EDIT(690),
+ @UiEvent(doc = "A long screenshot capture has started")
+ SCREENSHOT_LONG_SCREENSHOT_STARTED(880),
+ @UiEvent(doc = "The long screenshot capture failed")
+ SCREENSHOT_LONG_SCREENSHOT_FAILURE(881),
+ @UiEvent(doc = "The long screenshot capture completed successfully")
+ SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882),
+ @UiEvent(doc = "Long screenshot editor activity started")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED(889),
+ @UiEvent(doc = "Long screenshot editor activity loaded a previously saved screenshot")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED(890),
+ @UiEvent(doc = "Long screenshot editor activity finished")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED(891);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e9e62f2..dfb39e3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -118,8 +118,8 @@
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183;
+ private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350;
+ private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350;
private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final float ROUNDED_CORNER_RADIUS = .25f;
@@ -242,19 +242,21 @@
/**
* Called to display the scroll action chip when support is detected.
*
+ * @param packageName the owning package of the window to be captured
* @param onClick the action to take when the chip is clicked.
*/
- public void showScrollChip(Runnable onClick) {
+ public void showScrollChip(String packageName, Runnable onClick) {
if (DEBUG_SCROLL) {
Log.d(TAG, "Showing Scroll option");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION);
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
mScrollChip.setVisibility(VISIBLE);
mScrollChip.setOnClickListener((v) -> {
if (DEBUG_INPUT) {
Log.d(TAG, "scroll chip tapped");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED);
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ packageName);
onClick.run();
});
}
@@ -414,21 +416,30 @@
mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
}
- void updateDisplayCutoutMargins(DisplayCutout cutout) {
+ void updateInsets(WindowInsets insets) {
int orientation = mContext.getResources().getConfiguration().orientation;
mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
FrameLayout.LayoutParams p =
(FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams();
+ DisplayCutout cutout = insets.getDisplayCutout();
+ Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
if (cutout == null) {
- p.setMargins(0, 0, 0, 0);
+ p.setMargins(0, 0, 0, navBarInsets.bottom);
} else {
Insets waterfall = cutout.getWaterfallInsets();
if (mOrientationPortrait) {
- p.setMargins(waterfall.left, Math.max(cutout.getSafeInsetTop(), waterfall.top),
- waterfall.right, Math.max(cutout.getSafeInsetBottom(), waterfall.bottom));
+ p.setMargins(
+ waterfall.left,
+ Math.max(cutout.getSafeInsetTop(), waterfall.top),
+ waterfall.right,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
} else {
- p.setMargins(Math.max(cutout.getSafeInsetLeft(), waterfall.left), waterfall.top,
- Math.max(cutout.getSafeInsetRight(), waterfall.right), waterfall.bottom);
+ p.setMargins(
+ Math.max(cutout.getSafeInsetLeft(), waterfall.left),
+ waterfall.top,
+ Math.max(cutout.getSafeInsetRight(), waterfall.right),
+ Math.max(navBarInsets.bottom, waterfall.bottom));
}
}
mStaticLeftMargin = p.leftMargin;
@@ -436,10 +447,10 @@
mScreenshotStatic.requestLayout();
}
- void updateOrientation(DisplayCutout cutout) {
+ void updateOrientation(WindowInsets insets) {
int orientation = mContext.getResources().getConfiguration().orientation;
mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
- updateDisplayCutoutMargins(cutout);
+ updateInsets(insets);
int screenshotFixedSize =
mContext.getResources().getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
@@ -978,7 +989,6 @@
mScrollingScrim.setVisibility(View.GONE);
mScrollablePreview.setVisibility(View.GONE);
mScreenshotStatic.setTranslationX(0);
- mScreenshotPreview.setTranslationY(0);
mScreenshotPreview.setContentDescription(
mContext.getResources().getString(R.string.screenshot_preview_description));
mScreenshotPreview.setOnClickListener(null);
@@ -994,9 +1004,6 @@
mSmartChips.clear();
mQuickShareChip = null;
setAlpha(1);
- mDismissButton.setTranslationY(0);
- mActionsContainer.setTranslationY(0);
- mActionsContainerBackground.setTranslationY(0);
mScreenshotSelectorView.stop();
}
@@ -1024,22 +1031,19 @@
setAlpha(1 - animation.getAnimatedFraction());
});
- ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
- yAnim.setInterpolator(mAccelerateInterpolator);
- yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
- float screenshotStartY = mScreenshotPreview.getTranslationY();
- float dismissStartY = mDismissButton.getTranslationY();
- yAnim.addUpdateListener(animation -> {
- float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
- mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
- mScreenshotPreviewBorder.setTranslationY(screenshotStartY + yDelta);
- mDismissButton.setTranslationY(dismissStartY + yDelta);
- mActionsContainer.setTranslationY(yDelta);
- mActionsContainerBackground.setTranslationY(yDelta);
+ ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1);
+ xAnim.setInterpolator(mAccelerateInterpolator);
+ xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
+ float deltaX = mDirectionLTR
+ ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
+ : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
+ xAnim.addUpdateListener(animation -> {
+ float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
+ mScreenshotStatic.setTranslationX(currXDelta);
});
AnimatorSet animSet = new AnimatorSet();
- animSet.play(yAnim).with(alphaAnim);
+ animSet.play(xAnim).with(alphaAnim);
return animSet;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 6dc6874..ef7355a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -28,6 +28,7 @@
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -61,6 +62,7 @@
private final Context mContext;
private final Executor mBgExecutor;
private final ImageTileSet mImageTileSet;
+ private final UiEventLogger mEventLogger;
private final ScrollCaptureClient mClient;
private Completer<LongScreenshot> mCaptureCompleter;
@@ -69,6 +71,7 @@
private Session mSession;
private ListenableFuture<CaptureResult> mTileFuture;
private ListenableFuture<Void> mEndFuture;
+ private String mWindowOwner;
static class LongScreenshot {
private final ImageTileSet mImageTileSet;
@@ -135,11 +138,12 @@
@Inject
ScrollCaptureController(Context context, @Background Executor bgExecutor,
- ScrollCaptureClient client, ImageTileSet imageTileSet) {
+ ScrollCaptureClient client, ImageTileSet imageTileSet, UiEventLogger logger) {
mContext = context;
mBgExecutor = bgExecutor;
mClient = client;
mImageTileSet = imageTileSet;
+ mEventLogger = logger;
}
@VisibleForTesting
@@ -157,6 +161,7 @@
ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
return CallbackToFutureAdapter.getFuture(completer -> {
mCaptureCompleter = completer;
+ mWindowOwner = response.getPackageName();
mBgExecutor.execute(() -> {
float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
@@ -173,11 +178,13 @@
if (LogConfig.DEBUG_SCROLL) {
Log.d(TAG, "got session " + mSession);
}
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_STARTED, 0, mWindowOwner);
requestNextTile(0);
} catch (InterruptedException | ExecutionException e) {
// Failure to start, propagate to caller
Log.e(TAG, "session start failed!");
mCaptureCompleter.setException(e);
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
}
}
@@ -297,6 +304,11 @@
if (LogConfig.DEBUG_SCROLL) {
Log.d(TAG, "finishCapture()");
}
+ if (mImageTileSet.getHeight() > 0) {
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_COMPLETED, 0, mWindowOwner);
+ } else {
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
+ }
mEndFuture = mSession.end();
mEndFuture.addListener(() -> {
if (LogConfig.DEBUG_SCROLL) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 8e52b0d..50911d16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -290,8 +290,8 @@
default void showAuthenticationDialog(PromptInfo promptInfo,
IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed,
- boolean requireConfirmation, int userId, String opPackageName,
- long operationId, @BiometricMultiSensorMode int multiSensorConfig) {
+ boolean requireConfirmation, int userId, long operationId, String opPackageName,
+ long requestId, @BiometricMultiSensorMode int multiSensorConfig) {
}
/** @see IStatusBar#onBiometricAuthenticated() */
@@ -843,7 +843,7 @@
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
@@ -855,6 +855,7 @@
args.argi1 = userId;
args.arg6 = opPackageName;
args.arg7 = operationId;
+ args.arg8 = requestId;
args.argi2 = multiSensorConfig;
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
.sendToTarget();
@@ -1312,8 +1313,9 @@
(boolean) someArgs.arg4 /* credentialAllowed */,
(boolean) someArgs.arg5 /* requireConfirmation */,
someArgs.argi1 /* userId */,
- (String) someArgs.arg6 /* opPackageName */,
(long) someArgs.arg7 /* operationId */,
+ (String) someArgs.arg6 /* opPackageName */,
+ (long) someArgs.arg8 /* requestId */,
someArgs.argi2 /* multiSensorConfig */);
}
someArgs.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 1c93350..8a39719 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -66,6 +66,7 @@
import com.android.internal.widget.ViewClippingUtil;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -334,7 +335,7 @@
info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
}
}
- if (info != null) {
+ if (!TextUtils.isEmpty(info)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_OWNER_INFO,
new KeyguardIndication.Builder()
@@ -432,7 +433,7 @@
}
private void updateResting() {
- if (mRestingIndication != null
+ if (!TextUtils.isEmpty(mRestingIndication)
&& !mRotateTextViewController.hasIndications()) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_RESTING,
@@ -455,7 +456,8 @@
new KeyguardIndication.Builder()
.setMessage(mContext.getResources().getString(
com.android.internal.R.string.global_action_logout))
- .setTextColor(mInitialTextColorState)
+ .setTextColor(Utils.getColorAttr(
+ mContext, com.android.internal.R.attr.textColorOnAccent))
.setBackground(mContext.getDrawable(
com.android.systemui.R.drawable.logout_button_background))
.setClickListener((view) -> {
@@ -723,15 +725,13 @@
}
protected String computePowerIndication() {
- if (mPowerCharged) {
- return mContext.getResources().getString(R.string.keyguard_charged);
- }
-
int chargingId;
- String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
if (mBatteryOverheated) {
chargingId = R.string.keyguard_plugged_in_charging_limited;
+ String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(chargingId, percentage);
+ } else if (mPowerCharged) {
+ return mContext.getResources().getString(R.string.keyguard_charged);
}
final boolean hasChargingTime = mChargingTimeRemaining > 0;
@@ -759,6 +759,7 @@
: R.string.keyguard_plugged_in_wireless;
}
+ String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
if (hasChargingTime) {
String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
mContext, mChargingTimeRemaining);
@@ -799,7 +800,9 @@
* Show message on the keyguard for how the user can unlock/enter their device.
*/
public void showActionToUnlock() {
- if (mDozing) {
+ if (mDozing
+ && !mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+ KeyguardUpdateMonitor.getCurrentUser())) {
return;
}
@@ -810,13 +813,13 @@
String message = mContext.getString(R.string.keyguard_retry);
mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
}
- } else if (mKeyguardUpdateMonitor.isScreenOn()) {
+ } else {
showTransientIndication(mContext.getString(R.string.keyguard_unlock),
false /* isError */, true /* hideOnScreenOff */);
}
}
- private void showTryFingerprintMsg() {
+ private void showTryFingerprintMsg(String a11yString) {
if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
// if udfps available, there will always be a tappable affordance to unlock
// For example, the lock icon
@@ -828,6 +831,11 @@
} else {
showTransientIndication(R.string.keyguard_try_fingerprint);
}
+
+ // Although we suppress face auth errors visually, we still announce them for a11y
+ if (!TextUtils.isEmpty(a11yString)) {
+ mLockScreenIndicationView.announceForAccessibility(a11yString);
+ }
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -908,7 +916,7 @@
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
if (biometricSourceType == BiometricSourceType.FACE
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(helpString);
return;
}
showTransientIndication(helpString, false /* isError */, showActionToUnlock);
@@ -928,7 +936,7 @@
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()
&& !mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isScreenOn()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -937,7 +945,7 @@
if (!mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isUdfpsEnrolled()
&& mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(errString);
} else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
mStatusBarKeyguardViewManager.showBouncerMessage(
mContext.getResources().getString(R.string.keyguard_unlock_press),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 538db61..21ed9da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -149,7 +149,10 @@
*/
class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- lateinit var revealAmountListener: Consumer<Float>
+ /**
+ * Listener that is called if the scrim's opaqueness changes
+ */
+ lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
/**
* How much of the underlying views are revealed, in percent. 0 means they will be completely
@@ -161,7 +164,7 @@
field = value
revealEffect.setRevealAmountOnScrim(value, this)
- revealAmountListener.accept(value)
+ updateScrimOpaque()
invalidate()
}
}
@@ -201,6 +204,31 @@
}
/**
+ * Is the scrim currently fully opaque
+ */
+ var isScrimOpaque = false
+ private set(value) {
+ if (field != value) {
+ field = value
+ isScrimOpaqueChangedListener.accept(field)
+ }
+ }
+
+ private fun updateScrimOpaque() {
+ isScrimOpaque = revealAmount == 0.0f && alpha == 1.0f && visibility == VISIBLE
+ }
+
+ override fun setAlpha(alpha: Float) {
+ super.setAlpha(alpha)
+ updateScrimOpaque()
+ }
+
+ override fun setVisibility(visibility: Int) {
+ super.setVisibility(visibility)
+ updateScrimOpaque()
+ }
+
+ /**
* Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated
* via local matrix in [onDraw] so we never need to construct a new shader.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index b833427..2a8771e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -78,7 +78,8 @@
private var keyguardAnimator: Animator? = null
private var notificationAnimator: Animator? = null
private var updateScheduled: Boolean = false
- private var shadeExpansion = 0f
+ @VisibleForTesting
+ var shadeExpansion = 0f
private var isClosed: Boolean = true
private var isOpen: Boolean = false
private var isBlurred: Boolean = false
@@ -92,6 +93,9 @@
// Only for dumpsys
private var lastAppliedBlur = 0
+ // Shade expansion offset that happens when pulling down on a HUN.
+ var panelPullDownMinFraction = 0f
+
var shadeAnimation = DepthAnimation()
@VisibleForTesting
@@ -181,7 +185,8 @@
if (shouldApplyShadeBlur()) shadeExpansion else 0f, false))
var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION +
animationRadius * ANIMATION_BLUR_FRACTION)
- val qsExpandedRatio = qsPanelExpansion * shadeExpansion
+ val qsExpandedRatio = Interpolators.getNotificationScrimAlpha(qsPanelExpansion,
+ false /* notification */) * shadeExpansion
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
@@ -311,8 +316,10 @@
/**
* Update blurs when pulling down the shade
*/
- override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
+ override fun onPanelExpansionChanged(rawExpansion: Float, tracking: Boolean) {
val timestamp = SystemClock.elapsedRealtimeNanos()
+ val expansion = MathUtils.saturate(
+ (rawExpansion - panelPullDownMinFraction) / (1f - panelPullDownMinFraction))
if (shadeExpansion == expansion && prevTracking == tracking) {
prevTimestamp = timestamp
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index f0d779c..6ea79af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -182,10 +182,10 @@
default void setFaceAuthDisplayBrightness(float brightness) {}
/**
- * How much {@link LightRevealScrim} obscures the UI.
- * @param amount 0 when opaque, 1 when not transparent
+ * If {@link LightRevealScrim} obscures the UI.
+ * @param opaque if the scrim is opaque
*/
- default void setLightRevealScrimAmount(float amount) {}
+ default void setLightRevealScrimOpaque(boolean opaque) {}
/**
* Custom listener to pipe data back to plugins about whether or not the status bar would be
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
index 4a467ce..d01fc93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -104,7 +104,7 @@
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 1.5f
+ (1 - rippleShader.progress)) * radius * 2
canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 71546ae..0773460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -24,7 +24,6 @@
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
-import android.content.pm.UserInfo
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
@@ -45,6 +44,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
import java.lang.RuntimeException
@@ -67,6 +67,7 @@
private val contentResolver: ContentResolver,
private val configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
+ private val deviceProvisionedController: DeviceProvisionedController,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Main private val handler: Handler,
@@ -83,6 +84,55 @@
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
+ private val deviceProvisionedListener =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onDeviceProvisionedChanged() {
+ connectSession()
+ }
+
+ override fun onUserSetupChanged() {
+ connectSession()
+ }
+ }
+
+ private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
+ execution.assertIsMainThread()
+ val filteredTargets = targets.filter(::filterSmartspaceTarget)
+ plugin?.onTargetsAvailable(filteredTargets)
+ }
+
+ private val userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val settingsObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val configChangeListener = object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ execution.assertIsMainThread()
+ updateTextColorFromWallpaper()
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ execution.assertIsMainThread()
+ smartspaceView.setDozeAmount(eased)
+ }
+ }
+
+ init {
+ deviceProvisionedController.addCallback(deviceProvisionedListener)
+ }
+
fun isEnabled(): Boolean {
execution.assertIsMainThread()
@@ -141,13 +191,23 @@
}
private fun connectSession() {
- if (plugin == null || session != null) {
+ if (plugin == null || session != null || !this::smartspaceView.isInitialized) {
return
}
- val session = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, "lockscreen").build())
- session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ // Only connect after the device is fully provisioned to avoid connection caching
+ // issues
+ if (!deviceProvisionedController.isDeviceProvisioned() ||
+ !deviceProvisionedController.isCurrentUserSetup()) {
+ return
+ }
+
+ val newSession = smartspaceManager.createSmartspaceSession(
+ SmartspaceConfig.Builder(context, "lockscreen").build())
+ newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ this.session = newSession
+
+ deviceProvisionedController.removeCallback(deviceProvisionedListener)
userTracker.addCallback(userTrackerCallback, uiExecutor)
contentResolver.registerContentObserver(
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -158,8 +218,6 @@
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
- this.session = session
-
reloadSmartspace()
}
@@ -198,43 +256,6 @@
plugin?.unregisterListener(listener)
}
- private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
- execution.assertIsMainThread()
- val filteredTargets = targets.filter(::filterSmartspaceTarget)
- plugin?.onTargetsAvailable(filteredTargets)
- }
-
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
-
- override fun onProfilesChanged(profiles: List<UserInfo>) {
- }
- }
-
- private val settingsObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
- }
-
- private val configChangeListener = object : ConfigurationController.ConfigurationListener {
- override fun onThemeChanged() {
- execution.assertIsMainThread()
- updateTextColorFromWallpaper()
- }
- }
-
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- execution.assertIsMainThread()
- smartspaceView.setDozeAmount(eased)
- }
- }
-
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
return when (t.userHandle) {
userTracker.userHandle -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index dba3401..9c755e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -85,23 +85,26 @@
}
/**
- * Set the content of this view to be visible in an animated way.
- *
- * @param contentVisible True if the content should be visible or false if it should be hidden.
+ * @param visible True if we should animate contents visible
*/
- public void setContentVisible(boolean contentVisible) {
- setContentVisible(contentVisible, true /* animate */);
+ public void setContentVisible(boolean visible) {
+ setContentVisible(visible, true /* animate */, null /* runAfter */);
}
+
/**
- * Set the content of this view to be visible.
- * @param contentVisible True if the content should be visible or false if it should be hidden.
- * @param animate Should an animation be performed.
+ * @param visible True if the contents should be visible
+ * @param animate True if we should fade to new visibility
+ * @param runAfter Runnable to run after visibility updates
*/
- private void setContentVisible(boolean contentVisible, boolean animate) {
- if (mContentVisible != contentVisible) {
+ public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) {
+ if (mContentVisible != visible) {
mContentAnimating = animate;
- mContentVisible = contentVisible;
- setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
+ mContentVisible = visible;
+ Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> {
+ mContentVisibilityEndRunnable.run();
+ runAfter.run();
+ };
+ setViewVisible(mContent, visible, animate, endRunnable);
}
if (!mContentAnimating) {
@@ -113,6 +116,10 @@
return mContentVisible;
}
+ public void setVisible(boolean nowVisible, boolean animate) {
+ setVisible(nowVisible, animate, null);
+ }
+
/**
* Make this view visible. If {@code false} is passed, the view will fade out it's content
* and set the view Visibility to GONE. If only the content should be changed
@@ -121,7 +128,7 @@
* @param nowVisible should the view be visible
* @param animate should the change be animated.
*/
- public void setVisible(boolean nowVisible, boolean animate) {
+ public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) {
if (mIsVisible != nowVisible) {
mIsVisible = nowVisible;
if (animate) {
@@ -132,10 +139,10 @@
} else {
setWillBeGone(true);
}
- setContentVisible(nowVisible, true /* animate */);
+ setContentVisible(nowVisible, true /* animate */, runAfter);
} else {
setVisibility(nowVisible ? VISIBLE : GONE);
- setContentVisible(nowVisible, false /* animate */);
+ setContentVisible(nowVisible, false /* animate */, runAfter);
setWillBeGone(false);
notifyHeightChanged(false /* needsAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 23aefd9..594afce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -46,6 +46,7 @@
private Runnable mRoundingChangedCallback;
private ExpandableNotificationRow mTrackedHeadsUp;
private float mAppearFraction;
+ private boolean mIsDismissAllInProgress;
private ExpandableView mSwipedView = null;
private ExpandableView mViewBeforeSwipedView = null;
@@ -162,6 +163,10 @@
}
}
+ void setDismissAllInProgress(boolean isClearingAll) {
+ mIsDismissAllInProgress = isClearingAll;
+ }
+
private float getRoundness(ExpandableView view, boolean top) {
if (view == null) {
return 0f;
@@ -171,6 +176,11 @@
|| view == mViewAfterSwipedView) {
return 1f;
}
+ if (view instanceof ExpandableNotificationRow
+ && ((ExpandableNotificationRow) view).canViewBeDismissed()
+ && mIsDismissAllInProgress) {
+ return 1.0f;
+ }
if ((view.isPinned()
|| (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
return 1.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index ae85205..733c0a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -144,6 +144,10 @@
private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean(
"persist.debug.nssl.dismiss", false /* default */);
+ // Delay in milli-seconds before shade closes for clear all.
+ private final int DELAY_BEFORE_SHADE_CLOSE = 200;
+ private boolean mShadeNeedsToClose = false;
+
private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
@@ -260,7 +264,6 @@
protected FooterView mFooterView;
protected EmptyShadeView mEmptyShadeView;
private boolean mDismissAllInProgress;
- private boolean mFadeNotificationsOnDismiss;
private FooterDismissListener mFooterDismissListener;
private boolean mFlingAfterUpEvent;
@@ -613,6 +616,7 @@
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
mGroupMembershipManager = groupMembershipManager;
mGroupExpansionManager = groupExpansionManager;
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) {
@@ -1194,7 +1198,7 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void clampScrollPosition() {
int scrollRange = getScrollRange();
- if (scrollRange < mOwnScrollY) {
+ if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) {
boolean animateStackY = false;
if (scrollRange < getScrollAmountToScrollBoundary()
&& mAnimateStackYForContentHeightChange) {
@@ -1710,6 +1714,11 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
+ if (child instanceof SectionHeaderView) {
+ ((StackScrollerDecorView) child).setContentVisible(
+ false /* visible */, true /* animate */, endRunnable);
+ return;
+ }
mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
true /* isDismissAll */);
}
@@ -4052,6 +4061,19 @@
runAnimationFinishedRunnables();
clearTransient();
clearHeadsUpDisappearRunning();
+
+ if (mAmbientState.isDismissAllInProgress()) {
+ setDismissAllInProgress(false);
+
+ if (mShadeNeedsToClose) {
+ mShadeNeedsToClose = false;
+ postDelayed(
+ () -> {
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ },
+ DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
+ }
+ }
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4409,6 +4431,7 @@
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
mAmbientState.setDismissAllInProgress(dismissAllInProgress);
+ mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress);
handleDismissAllClipping();
}
@@ -4949,64 +4972,129 @@
mHeadsUpAppearanceController = headsUpAppearanceController;
}
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- @VisibleForTesting
- void clearNotifications(@SelectedRows int selection, boolean closeShade) {
- // animate-swipe all dismissable notifications, then animate the shade closed
- int numChildren = getChildCount();
+ private boolean isVisible(View child) {
+ boolean hasClipBounds = child.getClipBounds(mTmpRect);
+ return child.getVisibility() == View.VISIBLE
+ && (!hasClipBounds || mTmpRect.height() > 0);
+ }
- final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
- final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean parentVisible = false;
- boolean hasClipBounds = child.getClipBounds(mTmpRect);
- if (includeChildInDismissAll(row, selection)) {
- viewsToRemove.add(row);
- if (child.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- viewsToHide.add(child);
- parentVisible = true;
- }
- } else if (child.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- parentVisible = true;
- }
- List<ExpandableNotificationRow> children = row.getAttachedChildren();
- if (children != null) {
- for (ExpandableNotificationRow childRow : children) {
- if (includeChildInDismissAll(row, selection)) {
- viewsToRemove.add(childRow);
- if (parentVisible && row.areChildrenExpanded()) {
- hasClipBounds = childRow.getClipBounds(mTmpRect);
- if (childRow.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- viewsToHide.add(childRow);
- }
- }
+ private boolean shouldHideParent(View view, @SelectedRows int selection) {
+ final boolean silentSectionWillBeGone =
+ !mController.hasNotifications(ROWS_GENTLE, false /* clearable */);
+
+ // The only SectionHeaderView we have is the silent section header.
+ if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
+ return true;
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (isVisible(row) && includeChildInDismissAll(row, selection)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isChildrenVisible(ExpandableNotificationRow parent) {
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+ return isVisible(parent)
+ && children != null
+ && parent.areChildrenExpanded();
+ }
+
+ // Similar to #getRowsToDismissInBackend, but filters for visible views.
+ private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) {
+ final int viewCount = getChildCount();
+ final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);
+
+ for (int i = 0; i < viewCount; i++) {
+ final View view = getChildAt(i);
+
+ if (shouldHideParent(view, selection)) {
+ viewsToHide.add(view);
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+
+ if (isChildrenVisible(parent)) {
+ for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
+ if (isVisible(child) && includeChildInDismissAll(child, selection)) {
+ viewsToHide.add(child);
}
}
}
}
}
+ return viewsToHide;
+ }
+ private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend(
+ @SelectedRows int selection) {
+ final int childCount = getChildCount();
+ final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(childCount);
+
+ for (int i = 0; i < childCount; i++) {
+ final View view = getChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ if (includeChildInDismissAll(parent, selection)) {
+ viewsToRemove.add(parent);
+ }
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+ if (isVisible(parent) && children != null) {
+ for (ExpandableNotificationRow child : children) {
+ if (includeChildInDismissAll(parent, selection)) {
+ viewsToRemove.add(child);
+ }
+ }
+ }
+ }
+ return viewsToRemove;
+ }
+
+ /**
+ * Collects a list of visible rows, and animates them away in a staggered fashion as if they
+ * were dismissed. Notifications are dismissed in the backend via onDismissAllAnimationsEnd.
+ */
+ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
+ @VisibleForTesting
+ void clearNotifications(@SelectedRows int selection, boolean closeShade) {
+ // Animate-swipe all dismissable notifications, then animate the shade closed
+ final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
+ final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
+ getRowsToDismissInBackend(selection);
if (mDismissListener != null) {
mDismissListener.onDismiss(selection);
}
-
- if (viewsToRemove.isEmpty()) {
- if (closeShade && mShadeController != null) {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- }
+ final Runnable dismissInBackend = () -> {
+ onDismissAllAnimationsEnd(rowsToDismissInBackend, selection);
+ };
+ if (viewsToAnimateAway.isEmpty()) {
+ dismissInBackend.run();
return;
}
+ // Disable normal animations
+ setDismissAllInProgress(true);
+ mShadeNeedsToClose = closeShade;
- performDismissAllAnimations(
- viewsToHide,
- closeShade,
- () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
+ // Decrease the delay for every row we animate to give the sense of
+ // accelerating the swipes
+ final int rowDelayDecrement = 5;
+ int currentDelay = 60;
+ int totalDelay = 0;
+ final int numItems = viewsToAnimateAway.size();
+ for (int i = numItems - 1; i >= 0; i--) {
+ View view = viewsToAnimateAway.get(i);
+ Runnable endRunnable = null;
+ if (i == 0) {
+ endRunnable = dismissInBackend;
+ }
+ dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
+ currentDelay = Math.max(30, currentDelay - rowDelayDecrement);
+ totalDelay += currentDelay;
+ }
}
private boolean includeChildInDismissAll(
@@ -5015,63 +5103,6 @@
return canChildBeDismissed(row) && matchesSelection(row, selection);
}
- /**
- * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
- * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
- * handler.
- *
- * @param hideAnimatedList List of rows to animated away. Should only be views that are
- * currently visible, or else the stagger will look funky.
- * @param closeShade Whether to close the shade after the stagger animation completes.
- * @param onAnimationComplete Called after the entire animation completes (including the shade
- * closing if appropriate). The rows must be dismissed for real here.
- */
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- private void performDismissAllAnimations(
- final ArrayList<View> hideAnimatedList,
- final boolean closeShade,
- final Runnable onAnimationComplete) {
-
- final Runnable onSlideAwayAnimationComplete = () -> {
- if (closeShade) {
- mShadeController.addPostCollapseAction(() -> {
- setDismissAllInProgress(false);
- onAnimationComplete.run();
- });
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE);
- } else {
- setDismissAllInProgress(false);
- onAnimationComplete.run();
- }
- };
-
- if (hideAnimatedList.isEmpty()) {
- onSlideAwayAnimationComplete.run();
- return;
- }
-
- // let's disable our normal animations
- setDismissAllInProgress(true);
-
- // Decrease the delay for every row we animate to give the sense of
- // accelerating the swipes
- int rowDelayDecrement = 10;
- int currentDelay = 140;
- int totalDelay = 180;
- int numItems = hideAnimatedList.size();
- for (int i = numItems - 1; i >= 0; i--) {
- View view = hideAnimatedList.get(i);
- Runnable endRunnable = null;
- if (i == 0) {
- endRunnable = onSlideAwayAnimationComplete;
- }
- dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
- currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
- totalDelay += currentDelay;
- }
- }
-
public void setNotificationActivityStarter(
NotificationActivityStarter notificationActivityStarter) {
mNotificationActivityStarter = notificationActivityStarter;
@@ -5087,6 +5118,7 @@
mFooterDismissListener.onDismiss();
}
clearNotifications(ROWS_ALL, true /* closeShade */);
+ footerView.setSecondaryVisible(false /* visible */, true /* animate */);
});
footerView.setManageButtonClickListener(v -> {
mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 3ceb655..1e92ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -174,6 +174,7 @@
private final NotificationLockscreenUserManager mLockscreenUserManager;
// TODO: StatusBar should be encapsulated behind a Controller
private final StatusBar mStatusBar;
+ private final NotificationGroupManagerLegacy mLegacyGroupManager;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@@ -678,6 +679,8 @@
mStatusBar.requestNotificationUpdate("onGroupsChanged");
}
});
+ mLegacyGroupManager = featureFlags.isNewNotifPipelineRenderingEnabled()
+ ? null : legacyGroupManager;
mSilentHeaderController = silentHeaderController;
mFeatureFlags = featureFlags;
mNotifPipeline = notifPipeline;
@@ -1152,6 +1155,10 @@
mZenModeController.areNotificationsHiddenInShade());
}
+ public boolean areNotificationsHiddenInShade() {
+ return mZenModeController.areNotificationsHiddenInShade();
+ }
+
public boolean isShowingEmptyShadeView() {
return mShowEmptyShadeView;
}
@@ -1200,6 +1207,10 @@
* Return whether there are any clearable notifications
*/
public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
+ return hasNotifications(selection, true /* clearable */);
+ }
+
+ public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
if (mDynamicPrivacyController.isInLockedDownShade()) {
return false;
}
@@ -1210,9 +1221,16 @@
continue;
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (row.canViewBeDismissed() &&
- NotificationStackScrollLayout.matchesSelection(row, selection)) {
- return true;
+ final boolean matchClearable =
+ isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed();
+ final boolean inSection =
+ NotificationStackScrollLayout.matchesSelection(row, selection);
+ if (matchClearable && inSection) {
+ if (mLegacyGroupManager == null
+ || !mLegacyGroupManager.isSummaryOfSuppressedGroup(
+ row.getEntry().getSbn())) {
+ return true;
+ }
}
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 2c810c9..8be5de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -366,6 +366,20 @@
return stackHeight / stackEndHeight;
}
+ public boolean hasOngoingNotifs(StackScrollAlgorithmState algorithmState) {
+ for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
+ View child = algorithmState.visibleChildren.get(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (!row.canViewBeDismissed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
@@ -430,7 +444,9 @@
+ view.getIntrinsicHeight();
final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
((FooterView.FooterViewState) viewState).hideContent =
- isShelfShowing || noSpaceForFooter;
+ isShelfShowing || noSpaceForFooter
+ || (ambientState.isDismissAllInProgress()
+ && !hasOngoingNotifs(algorithmState));
}
} else {
if (view != ambientState.getTrackedHeadsUpRow()) {
@@ -467,7 +483,6 @@
}
}
}
-
// Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index ee12b4b..4466cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -46,7 +46,7 @@
public static final int ANIMATION_DURATION_WAKEUP = 500;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
- public static final int ANIMATION_DURATION_SWIPE = 260;
+ public static final int ANIMATION_DURATION_SWIPE = 200;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
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 5a6db21..e67c6ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -291,6 +291,7 @@
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.print("getAlwaysOn(): "); pw.println(getAlwaysOn());
pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
pw.print("getPulseDuration(): "); pw.println(getPulseDuration());
pw.print("getPulseInDuration(): "); pw.println(getPulseInDuration());
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 b2cf72a..21c3e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -116,11 +116,18 @@
if (!mDozing || mPulseCallback != null) {
if (DEBUG) {
- Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+ Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? "
+ (mPulseCallback != null));
}
// Pulse suppressed.
callback.onPulseFinished();
+ if (!mDozing) {
+ mDozeLog.tracePulseDropped("device isn't dozing");
+ } else {
+ mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback="
+ + mPulseCallback);
+ }
+
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 0a4e59c..4701d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -203,13 +203,15 @@
private ControlsListingController.ControlsListingCallback mListingCallback =
new ControlsListingController.ControlsListingCallback() {
public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
- boolean available = !serviceInfos.isEmpty();
+ post(() -> {
+ boolean available = !serviceInfos.isEmpty();
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateControlsVisibility();
- updateAffordanceColors();
- }
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateControlsVisibility();
+ updateAffordanceColors();
+ }
+ });
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index f77c052..b58cab4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -83,8 +83,7 @@
private int mNotificationStackHeight;
/**
- * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
- * avatar.
+ * Minimum top margin to avoid overlap with status bar, or multi-user switcher avatar.
*/
private int mMinTopMargin;
@@ -150,6 +149,25 @@
private boolean mIsSplitShade;
/**
+ * Top location of the udfps icon. This includes the worst case (highest) burn-in
+ * offset that would make the top physically highest on the screen.
+ *
+ * Set to -1 if udfps is not enrolled on the device.
+ */
+ private float mUdfpsTop;
+
+ /**
+ * Bottom y-position of the currently visible clock
+ */
+ private float mClockBottom;
+
+ /**
+ * If true, try to keep clock aligned to the top of the display. Else, assume the clock
+ * is center aligned.
+ */
+ private boolean mIsClockTopAligned;
+
+ /**
* Refreshes the dimension values.
*/
public void loadDimens(Resources res) {
@@ -157,7 +175,7 @@
R.dimen.keyguard_status_view_bottom_margin);
mContainerTopPadding =
- res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) / 2;
+ res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
mBurnInPreventionOffsetX = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetY = res.getDimensionPixelSize(
@@ -174,7 +192,8 @@
int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
boolean hasCustomClock, boolean hasVisibleNotifs, float dark,
float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding,
- float qsExpansion, int cutoutTopInset, boolean isSplitShade) {
+ float qsExpansion, int cutoutTopInset, boolean isSplitShade, float udfpsTop,
+ float clockBottom, boolean isClockTopAligned) {
mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
userSwitchHeight);
mMaxShadeBottom = maxShadeBottom;
@@ -193,6 +212,9 @@
mQsExpansion = qsExpansion;
mCutoutTopInset = cutoutTopInset;
mIsSplitShade = isSplitShade;
+ mUdfpsTop = udfpsTop;
+ mClockBottom = clockBottom;
+ mIsClockTopAligned = isClockTopAligned;
}
public void run(Result result) {
@@ -247,8 +269,34 @@
if (clockY - mBurnInPreventionOffsetYLargeClock < mCutoutTopInset) {
shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYLargeClock);
}
- float clockYDark = clockY + burnInPreventionOffsetY() + shift;
+ int burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock; // requested offset
+ final boolean hasUdfps = mUdfpsTop > -1;
+ if (hasUdfps && !mIsClockTopAligned) {
+ // ensure clock doesn't overlap with the udfps icon
+ if (mUdfpsTop < mClockBottom) {
+ // sometimes the clock textView extends beyond udfps, so let's just use the
+ // space above the KeyguardStatusView/clock as our burn-in offset
+ burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
+ if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+ }
+ shift = -burnInPreventionOffsetY;
+ } else {
+ float upperSpace = clockY - mCutoutTopInset;
+ float lowerSpace = mUdfpsTop - mClockBottom;
+ // center the burn-in offset within the upper + lower space
+ burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
+ if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+ }
+ shift = (lowerSpace - upperSpace) / 2;
+ }
+ }
+
+ float clockYDark = clockY
+ + burnInPreventionOffsetY(burnInPreventionOffsetY)
+ + shift;
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
}
@@ -280,9 +328,7 @@
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
}
- private float burnInPreventionOffsetY() {
- int offset = mBurnInPreventionOffsetYLargeClock;
-
+ private float burnInPreventionOffsetY(int offset) {
return getBurnInOffset(offset * 2, false /* xAxis */) - offset;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index f1cde8a..a5b5f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -28,7 +28,10 @@
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.StyleRes;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.keyguard.KeyguardIndication;
@@ -39,6 +42,12 @@
*/
public class KeyguardIndicationTextView extends TextView {
private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500;
+
+ @StyleRes
+ private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
+ @StyleRes
+ private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button;
+
private long mNextAnimationTime = 0;
private boolean mAnimationsEnabled = true;
private LinkedList<CharSequence> mMessages = new LinkedList<>();
@@ -136,6 +145,14 @@
public void onAnimationEnd(Animator animator) {
KeyguardIndication info = mKeyguardIndicationInfo.poll();
if (info != null) {
+ // First, update the style.
+ // If a background is set on the text, we don't want shadow on the text
+ if (info.getBackground() != null) {
+ setTextAppearance(sButtonStyleId);
+ } else {
+ setTextAppearance(sStyleId);
+ }
+ setBackground(info.getBackground());
setTextColor(info.getTextColor());
setOnClickListener(info.getClickListener());
setClickable(info.getClickListener() != null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e272d27..55e0c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -96,7 +96,8 @@
private final UserManager mUserManager;
private int mSystemIconsSwitcherHiddenExpandedMargin;
- private int mSystemIconsBaseMargin;
+ private int mStatusBarPaddingEnd;
+ private int mMinDotWidth;
private View mSystemIconsContainer;
private TintedIconManager mIconManager;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -157,14 +158,7 @@
mMultiUserAvatar.setLayoutParams(lp);
// System icons
- lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
- lp.setMarginStart(getResources().getDimensionPixelSize(
- R.dimen.system_icons_super_container_margin_start));
- mSystemIconsContainer.setLayoutParams(lp);
- mSystemIconsContainer.setPaddingRelative(mSystemIconsContainer.getPaddingStart(),
- mSystemIconsContainer.getPaddingTop(),
- getResources().getDimensionPixelSize(R.dimen.system_icons_keyguard_padding_end),
- mSystemIconsContainer.getPaddingBottom());
+ updateSystemIconsLayoutParams();
// Respect font size setting.
mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
@@ -194,8 +188,10 @@
Resources res = getResources();
mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
R.dimen.system_icons_switcher_hidden_expanded_margin);
- mSystemIconsBaseMargin = res.getDimensionPixelSize(
- R.dimen.system_icons_super_container_avatarless_margin_end);
+ mStatusBarPaddingEnd = res.getDimensionPixelSize(
+ R.dimen.status_bar_padding_end);
+ mMinDotWidth = res.getDimensionPixelSize(
+ R.dimen.ongoing_appops_dot_min_padding);
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
mShowPercentAvailable = getContext().getResources().getBoolean(
@@ -243,16 +239,24 @@
private void updateSystemIconsLayoutParams() {
LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- // If the avatar icon is gone, we need to have some end margin to display the system icons
- // correctly.
- int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
- ? mSystemIconsBaseMargin
- : 0;
+
+ int marginStart = getResources().getDimensionPixelSize(
+ R.dimen.system_icons_super_container_margin_start);
+
+ // Use status_bar_padding_end to replace original
+ // system_icons_super_container_avatarless_margin_end to prevent different end alignment
+ // between PhoneStatusBarView and KeyguardStatusBarView
+ int baseMarginEnd = mStatusBarPaddingEnd;
int marginEnd =
mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
: baseMarginEnd;
- marginEnd = calculateMargin(marginEnd, mPadding.second);
- if (marginEnd != lp.getMarginEnd()) {
+
+ // Align PhoneStatusBar right margin/padding, only use
+ // 1. status bar layout: mPadding(consider round_corner + privacy dot)
+ // 2. icon container: R.dimen.status_bar_padding_end
+
+ if (marginEnd != lp.getMarginEnd() || marginStart != lp.getMarginStart()) {
+ lp.setMarginStart(marginStart);
lp.setMarginEnd(marginEnd);
mSystemIconsContainer.setLayoutParams(lp);
}
@@ -287,7 +291,13 @@
mPadding =
StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
- setPadding(mPadding.first, waterfallTop, mPadding.second, 0);
+
+ // consider privacy dot space
+ final int minLeft = isLayoutRtl() ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first;
+ final int minRight = isLayoutRtl() ? mPadding.second :
+ Math.max(mMinDotWidth, mPadding.second);
+
+ setPadding(minLeft, waterfallTop, minRight, 0);
}
private boolean updateLayoutParamsNoCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 440f19c..58cbe83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -51,6 +51,7 @@
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
@@ -621,6 +622,8 @@
*/
private float mKeyguardOnlyContentAlpha = 1.0f;
+ private float mUdfpsMaxYBurnInOffset;
+
/**
* Are we currently in gesture navigation
*/
@@ -635,6 +638,7 @@
private int mQsClipBottom;
private boolean mQsVisible;
private final ContentResolver mContentResolver;
+ private float mMinFraction;
private final Executor mUiExecutor;
private final SecureSettings mSecureSettings;
@@ -852,13 +856,13 @@
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
- UserAvatarView userAvatarView = null;
+ FrameLayout userAvatarContainer = null;
KeyguardUserSwitcherView keyguardUserSwitcherView = null;
if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
if (mKeyguardQsUserSwitchEnabled) {
ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
- userAvatarView = (UserAvatarView) stub.inflate();
+ userAvatarContainer = (FrameLayout) stub.inflate();
} else {
ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
@@ -867,7 +871,7 @@
updateViewControllers(
mView.findViewById(R.id.keyguard_status_view),
- userAvatarView,
+ userAvatarContainer,
mKeyguardStatusBar,
keyguardUserSwitcherView);
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
@@ -956,10 +960,11 @@
mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources);
mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
+ mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
- UserAvatarView userAvatarView,
+ FrameLayout userAvatarView,
KeyguardStatusBarView keyguardStatusBarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
@@ -1111,7 +1116,7 @@
!mKeyguardQsUserSwitchEnabled
&& mKeyguardUserSwitcherEnabled
&& isUserSwitcherEnabled;
- UserAvatarView userAvatarView = (UserAvatarView) reInflateStub(
+ FrameLayout userAvatarView = (FrameLayout) reInflateStub(
R.id.keyguard_qs_user_switch_view /* viewId */,
R.id.keyguard_qs_user_switch_stub /* stubId */,
R.layout.keyguard_qs_user_switch /* layoutId */,
@@ -1300,7 +1305,16 @@
float darkamount =
mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
? 1.0f : mInterpolatedDarkAmount;
- mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard,
+
+ float udfpsAodTopLocation = -1f;
+ if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) {
+ FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
+ udfpsAodTopLocation = props.sensorLocationY - props.sensorRadius
+ - mUdfpsMaxYBurnInOffset;
+ }
+
+ mClockPositionAlgorithm.setup(
+ mStatusBarHeaderHeightKeyguard,
totalHeight - bottomPadding,
mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
expandedFraction,
@@ -1312,7 +1326,10 @@
bypassEnabled, getUnlockedStackScrollerPadding(),
computeQsExpansionFraction(),
mDisplayTopInset,
- mShouldUseSplitNotificationShade);
+ mShouldUseSplitNotificationShade,
+ udfpsAodTopLocation,
+ mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
+ mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = animate || mAnimateNextPositionUpdate;
@@ -1795,6 +1812,15 @@
return !mQsTouchAboveFalsingThreshold;
}
+ /**
+ * Percentage of panel expansion offset, caused by pulling down on a heads-up.
+ */
+ @Override
+ public void setMinFraction(float minFraction) {
+ mMinFraction = minFraction;
+ mDepthController.setPanelPullDownMinFraction(mMinFraction);
+ }
+
private float computeQsExpansionFraction() {
if (mQSAnimatingHiddenFromCollapsed) {
// When hiding QS from collapsed state, the expansion can sometimes temporarily
@@ -2309,6 +2335,12 @@
}
}
top += mOverStretchAmount;
+ // Correction for instant expansion caused by HUN pull down/
+ if (mMinFraction > 0f && mMinFraction < 1f) {
+ float realFraction =
+ (getExpandedFraction() - mMinFraction) / (1f - mMinFraction);
+ top *= MathUtils.saturate(realFraction / mMinFraction);
+ }
bottom = getView().getBottom();
// notification bounds should take full screen width regardless of insets
left = 0;
@@ -3439,7 +3471,7 @@
}
public void setPanelScrimMinFraction(float minFraction) {
- mBar.panelScrimMinFractionChanged(minFraction);
+ mBar.onPanelMinFractionChanged(minFraction);
}
public void clearNotificationEffects() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 246810a..c26782b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -605,12 +605,11 @@
}
@Override
- public void setLightRevealScrimAmount(float amount) {
- boolean lightRevealScrimOpaque = amount == 0;
- if (mCurrentState.mLightRevealScrimOpaque == lightRevealScrimOpaque) {
+ public void setLightRevealScrimOpaque(boolean opaque) {
+ if (mCurrentState.mLightRevealScrimOpaque == opaque) {
return;
}
- mCurrentState.mLightRevealScrimOpaque = lightRevealScrimOpaque;
+ mCurrentState.mLightRevealScrimOpaque = opaque;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 66a6e72..3807b46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -325,6 +325,12 @@
// Capture all touch events in always-on.
return true;
}
+
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ // capture all touches if the alt auth bouncer is showing
+ return true;
+ }
+
boolean intercept = false;
if (mNotificationPanelViewController.isFullyExpanded()
&& mDragDownHelper.isDragDownEnabled()
@@ -352,6 +358,12 @@
if (mStatusBarStateController.isDozing()) {
handled = !mService.isPulsing();
}
+
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ // eat the touch
+ handled = true;
+ }
+
if ((mDragDownHelper.isDragDownEnabled() && !handled)
|| mDragDownHelper.isDraggingDown()) {
// we still want to finish our drag down gesture when locking the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index f1b6c7c..eca91a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -18,6 +18,7 @@
import static java.lang.Float.isNaN;
+import android.annotation.CallSuper;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
@@ -162,7 +163,13 @@
return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
}
- public abstract void panelScrimMinFractionChanged(float minFraction);
+ /**
+ * Percentage of panel expansion offset, caused by pulling down on a heads-up.
+ */
+ @CallSuper
+ public void onPanelMinFractionChanged(float minFraction) {
+ mPanel.setMinFraction(minFraction);
+ }
/**
* @param frac the fraction from the expansion in [0, 1]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 323a112..99c0c13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -338,6 +338,13 @@
protected abstract float getOpeningHeight();
/**
+ * Minimum fraction from where expansion should start. This is set when pulling down on a
+ * heads-up notification.
+ * @param minFraction Fraction from 0 to 1.
+ */
+ public abstract void setMinFraction(float minFraction);
+
+ /**
* @return whether the swiping direction is upwards and above a 45 degree angle compared to the
* horizontal direction
*/
@@ -1211,10 +1218,14 @@
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
addMovement(event);
- if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+ final boolean openShadeWithoutHun =
+ mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
+ if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
+ || openShadeWithoutHun) {
float hAbs = Math.abs(h);
float touchSlop = getTouchSlop(event);
- if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop))
+ if ((h < -touchSlop
+ || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
&& hAbs > Math.abs(x - mInitialTouchX)) {
cancelHeightAnimator();
startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
@@ -1227,10 +1238,7 @@
mVelocityTracker.clear();
break;
}
-
- // Finally, if none of the above cases applies, ensure that touches do not get handled
- // by the contents of a panel that is not showing (a bit of a hack to avoid b/178277858)
- return (mView.getVisibility() != View.VISIBLE);
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c300b11..2ca36b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -269,10 +269,11 @@
}
@Override
- public void panelScrimMinFractionChanged(float minFraction) {
+ public void onPanelMinFractionChanged(float minFraction) {
if (isNaN(minFraction)) {
throw new IllegalArgumentException("minFraction cannot be NaN");
}
+ super.onPanelMinFractionChanged(minFraction);
if (mMinFraction != minFraction) {
mMinFraction = minFraction;
updateScrimFraction();
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 7d25aee..27c129a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -587,6 +587,8 @@
if (isNaN(expansionFraction)) {
return;
}
+ expansionFraction = Interpolators
+ .getNotificationScrimAlpha(expansionFraction, false /* notification */);
boolean qsBottomVisible = qsPanelBottomY > 0;
if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
mQsExpansion = expansionFraction;
@@ -1262,6 +1264,8 @@
pw.println(mDefaultScrimAlpha);
pw.print(" mExpansionFraction=");
pw.println(mPanelExpansion);
+ pw.print(" mExpansionAffectsAlpha=");
+ pw.println(mExpansionAffectsAlpha);
pw.print(" mState.getMaxLightRevealScrimAlpha=");
pw.println(mState.getMaxLightRevealScrimAlpha());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 2c0de62..15b8c67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -279,15 +279,41 @@
BUBBLE_EXPANDED {
@Override
public void prepare(ScrimState previousState) {
- mFrontTint = Color.TRANSPARENT;
- mBehindTint = Color.TRANSPARENT;
- mBubbleTint = Color.BLACK;
+ mBehindAlpha = mClipQsScrim ? 1 : 0;
+ mNotifAlpha = 0;
+ mFrontAlpha = 0;
- mFrontAlpha = 0f;
- mBehindAlpha = mDefaultScrimAlpha;
+ mAnimationDuration = mKeyguardFadingAway
+ ? mKeyguardFadingAwayDuration
+ : StatusBar.FADE_KEYGUARD_DURATION;
+
+ mAnimateChange = !mLaunchingAffordanceWithPreview;
+
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.BLACK;
+ mBlankScreen = false;
+
+ if (previousState == ScrimState.AOD) {
+ // Set all scrims black, before they fade transparent.
+ updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+ if (mScrimForBubble != null) {
+ updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+ }
+
+ // Scrims should still be black at the end of the transition.
+ mFrontTint = Color.BLACK;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.BLACK;
+ mBlankScreen = true;
+ }
+
+ if (mClipQsScrim) {
+ updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ }
mAnimationDuration = ScrimController.ANIMATION_DURATION;
- mBlankScreen = false;
}
};
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 90fc779..88434cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1045,7 +1045,7 @@
mNotificationShadeWindowViewController,
mNotificationPanelViewController,
mAmbientIndicationContainer);
- mDozeParameters.addCallback(this::updateLightRevealScrimVisibility);
+ updateLightRevealScrimVisibility();
mConfigurationController.addCallback(this);
@@ -1262,8 +1262,19 @@
mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble);
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
- mLightRevealScrim.setRevealAmountListener(
- mNotificationShadeWindowController::setLightRevealScrimAmount);
+ mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
+ Runnable updateOpaqueness = () -> {
+ mNotificationShadeWindowController.setLightRevealScrimOpaque(
+ mLightRevealScrim.isScrimOpaque());
+ };
+ if (opaque) {
+ // Delay making the view opaque for a frame, because it needs some time to render
+ // otherwise this can lead to a flicker where the scrim doesn't cover the screen
+ mLightRevealScrim.post(updateOpaqueness);
+ } else {
+ updateOpaqueness.run();
+ }
+ });
mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim);
updateLightRevealScrimVisibility();
@@ -4455,8 +4466,11 @@
|| mKeyguardStateController.isKeyguardFadingAway();
// Do not animate the scrim expansion when triggered by the fingerprint sensor.
- mScrimController.setExpansionAffectsAlpha(
- !mBiometricUnlockController.isBiometricUnlock());
+ boolean onKeyguardOrHidingIt = mKeyguardStateController.isShowing()
+ || mKeyguardStateController.isKeyguardFadingAway()
+ || mKeyguardStateController.isKeyguardGoingAway();
+ mScrimController.setExpansionAffectsAlpha(!(mBiometricUnlockController.isBiometricUnlock()
+ && onKeyguardOrHidingIt));
boolean launchingAffordanceWithPreview =
mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
@@ -4958,11 +4972,5 @@
}
mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
- if (mFeatureFlags.useNewLockscreenAnimations()
- && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) {
- mLightRevealScrim.setVisibility(View.VISIBLE);
- } else {
- mLightRevealScrim.setVisibility(View.GONE);
- }
}
}
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 3188a52..1e3e47b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -53,6 +53,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.QuickStepContract;
@@ -92,7 +93,8 @@
// with the appear animations of the PIN/pattern/password views.
private static final long NAV_BAR_SHOW_DELAY_BOUNCER = 320;
- private static final long WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS = 200;
+ // The duration to fade the nav bar content in/out when the device starts to sleep
+ private static final long NAV_BAR_CONTENT_FADE_DURATION = 125;
// Duration of the Keyguard dismissal animation in case the user is currently locked. This is to
// make everything a bit slower to bridge a gap until the user is unlocked and home screen has
@@ -194,10 +196,8 @@
private boolean mLastGesturalNav;
private boolean mLastIsDocked;
private boolean mLastPulsing;
- private boolean mLastAnimatedToSleep;
private int mLastBiometricMode;
private boolean mQsExpanded;
- private boolean mAnimatedToSleep;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -304,8 +304,10 @@
* Sets a new alt auth interceptor.
*/
public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
- mAlternateAuthInterceptor = authInterceptor;
- resetAlternateAuth(false);
+ if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
+ mAlternateAuthInterceptor = authInterceptor;
+ resetAlternateAuth(false);
+ }
}
private void registerListeners() {
@@ -318,20 +320,6 @@
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
- mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
- @Override
- public void onFinishedWakingUp() {
- mAnimatedToSleep = false;
- updateStates();
- }
-
- @Override
- public void onFinishedGoingToSleep() {
- mAnimatedToSleep =
- mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
- updateStates();
- }
- });
}
@Override
@@ -564,12 +552,26 @@
public void onStartedWakingUp() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.setAnimationsDisabled(false);
+ View currentView = getCurrentNavBarView();
+ if (currentView != null) {
+ currentView.animate()
+ .alpha(1f)
+ .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+ .start();
+ }
}
@Override
public void onStartedGoingToSleep() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.setAnimationsDisabled(true);
+ View currentView = getCurrentNavBarView();
+ if (currentView != null) {
+ currentView.animate()
+ .alpha(0f)
+ .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+ .start();
+ }
}
@Override
@@ -991,10 +993,28 @@
mLastBiometricMode = mBiometricUnlockController.getMode();
mLastGesturalNav = mGesturalNav;
mLastIsDocked = mIsDocked;
- mLastAnimatedToSleep = mAnimatedToSleep;
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
+ /**
+ * Updates the visibility of the nav bar content views.
+ */
+ private void updateNavigationBarContentVisibility(boolean navBarContentVisible) {
+ final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+ if (navBarView != null && navBarView.getCurrentView() != null) {
+ final View currentView = navBarView.getCurrentView();
+ currentView.setVisibility(navBarContentVisible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private View getCurrentNavBarView() {
+ final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+ return navBarView != null ? navBarView.getCurrentView() : null;
+ }
+
+ /**
+ * Updates the visibility of the nav bar window (which will cause insets changes).
+ */
protected void updateNavigationBarVisibility(boolean navBarVisible) {
if (mStatusBar.getNavigationBarView() != null) {
if (navBarVisible) {
@@ -1022,7 +1042,7 @@
boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked)
&& mGesturalNav;
- return (!mAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
+ return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
|| mRemoteInputActive || keyguardWithGestureNav
|| mGlobalActionsVisible);
}
@@ -1035,7 +1055,7 @@
boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing
|| mLastPulsing && !mLastIsDocked) && mLastGesturalNav;
- return (!mLastAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mLastBouncerShowing
+ return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
|| mLastRemoteInputActive || keyguardWithGestureNav
|| mLastGlobalActionsVisible);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 47deb1f..8821de0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -310,17 +310,11 @@
private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
- if (old != null) {
- if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
- && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
- if (mStatusBarStateController.getState() == StatusBarState.SHADE
- && reason != NotificationListenerService.REASON_CLICK) {
- mCommandQueue.animateCollapsePanels();
- } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+ if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
+ && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
+ && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
&& !isCollapsing()) {
- mStatusBarStateController.setState(StatusBarState.KEYGUARD);
- }
- }
+ mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 6b52dca..143aaba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -5,7 +5,9 @@
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
+import android.database.ContentObserver
import android.os.Handler
+import android.provider.Settings
import android.view.View
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
@@ -19,6 +21,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
/**
@@ -46,15 +49,19 @@
private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
private val keyguardStateController: KeyguardStateController,
- private val dozeParameters: dagger.Lazy<DozeParameters>
+ private val dozeParameters: dagger.Lazy<DozeParameters>,
+ private val globalSettings: GlobalSettings
) : WakefulnessLifecycle.Observer {
private val handler = Handler()
private lateinit var statusBar: StatusBar
private lateinit var lightRevealScrim: LightRevealScrim
+ private var animatorDurationScale = 1f
+ private var shouldAnimateInKeyguard = false
private var lightRevealAnimationPlaying = false
private var aodUiAnimationPlaying = false
+ private var callbacks = HashSet<Callback>()
/**
* The result of our decision whether to play the screen off animation in
@@ -66,11 +73,17 @@
private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
duration = LIGHT_REVEAL_ANIMATION_DURATION
interpolator = Interpolators.LINEAR
- addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float }
+ addUpdateListener {
+ lightRevealScrim.revealAmount = it.animatedValue as Float
+ sendUnlockedScreenOffProgressUpdate(
+ 1f - (it.animatedFraction as Float),
+ 1f - (it.animatedValue as Float))
+ }
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator?) {
lightRevealScrim.revealAmount = 1f
lightRevealAnimationPlaying = false
+ sendUnlockedScreenOffProgressUpdate(0f, 0f)
}
override fun onAnimationEnd(animation: Animator?) {
@@ -79,6 +92,12 @@
})
}
+ val animatorDurationScaleObserver = object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ updateAnimatorDurationScale()
+ }
+ }
+
fun initialize(
statusBar: StatusBar,
lightRevealScrim: LightRevealScrim
@@ -86,14 +105,25 @@
this.lightRevealScrim = lightRevealScrim
this.statusBar = statusBar
+ updateAnimatorDurationScale()
+ globalSettings.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+ /* notify for descendants */ false,
+ animatorDurationScaleObserver)
wakefulnessLifecycle.addObserver(this)
}
+ fun updateAnimatorDurationScale() {
+ animatorDurationScale =
+ globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+ }
+
/**
* Animates in the provided keyguard view, ending in the same position that it will be in on
* AOD.
*/
fun animateInKeyguard(keyguardView: View, after: Runnable) {
+ shouldAnimateInKeyguard = false
keyguardView.alpha = 0f
keyguardView.visibility = View.VISIBLE
@@ -138,6 +168,7 @@
// Waking up, so reset this flag.
decidedToAnimateGoingToSleep = null
+ shouldAnimateInKeyguard = false
lightRevealAnimator.cancel()
handler.removeCallbacksAndMessages(null)
}
@@ -146,7 +177,6 @@
// Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other
// observers (such as StatusBar) can ask us whether we were playing the screen off animation
// and reset accordingly.
- lightRevealAnimationPlaying = false
aodUiAnimationPlaying = false
// If we can't control the screen off animation, we shouldn't mess with the StatusBar's
@@ -167,15 +197,15 @@
if (dozeParameters.get().shouldControlUnlockedScreenOff()) {
decidedToAnimateGoingToSleep = true
+ shouldAnimateInKeyguard = true
lightRevealAnimationPlaying = true
lightRevealAnimator.start()
-
handler.postDelayed({
aodUiAnimationPlaying = true
// Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
statusBar.notificationPanelViewController.showAodUi()
- }, ANIMATE_IN_KEYGUARD_DELAY)
+ }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
} else {
decidedToAnimateGoingToSleep = false
}
@@ -220,7 +250,21 @@
return true
}
- /**
+ fun addCallback(callback: Callback) {
+ callbacks.add(callback)
+ }
+
+ fun removeCallback(callback: Callback) {
+ callbacks.remove(callback)
+ }
+
+ fun sendUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) {
+ callbacks.forEach {
+ it.onUnlockedScreenOffProgressUpdate(linear, eased)
+ }
+ }
+
+/**
* Whether we're doing the light reveal animation or we're done with that and animating in the
* AOD UI.
*/
@@ -228,6 +272,10 @@
return lightRevealAnimationPlaying || aodUiAnimationPlaying
}
+ fun shouldAnimateInKeyguard(): Boolean {
+ return shouldAnimateInKeyguard
+ }
+
/**
* Whether the light reveal animation is playing. The second part of the screen off animation,
* where AOD animates in, might still be playing if this returns false.
@@ -235,4 +283,8 @@
fun isScreenOffLightRevealAnimationPlaying(): Boolean {
return lightRevealAnimationPlaying
}
-}
\ No newline at end of file
+
+ interface Callback {
+ fun onUnlockedScreenOffProgressUpdate(linear: Float, eased: Float)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 6982631..80a0a98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -104,7 +104,16 @@
}
}
+ // Fix for b/199600334
+ override fun onEntryCleanUp(entry: NotificationEntry) {
+ removeChipIfNeeded(entry)
+ }
+
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ removeChipIfNeeded(entry)
+ }
+
+ private fun removeChipIfNeeded(entry: NotificationEntry) {
if (entry.sbn.key == callNotificationInfo?.key) {
removeChip()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index ab58286..6d6320e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -40,6 +40,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
+import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
@@ -68,6 +69,7 @@
private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final Executor mMainExecutor;
private @Nullable WifiPickerTracker mWifiPickerTracker;
@@ -84,6 +86,7 @@
WifiPickerTrackerFactory wifiPickerTrackerFactory
) {
mUserManager = userManager;
+ mUserTracker = userTracker;
mCurrentUser = userTracker.getUserId();
mMainExecutor = mainExecutor;
mWifiPickerTrackerFactory = wifiPickerTrackerFactory;
@@ -118,6 +121,11 @@
new UserHandle(mCurrentUser));
}
+ public boolean canConfigMobileData() {
+ return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin();
+ }
+
public void onUserSwitched(int newUserId) {
mCurrentUser = newUserId;
}
@@ -157,6 +165,15 @@
}
@Override
+ public MergedCarrierEntry getMergedCarrierEntry() {
+ if (mWifiPickerTracker == null) {
+ fireAcccessPointsCallback(Collections.emptyList());
+ return null;
+ }
+ return mWifiPickerTracker.getMergedCarrierEntry();
+ }
+
+ @Override
public int getIcon(WifiEntry ap) {
int level = ap.getLevel();
return ICONS[Math.max(0, level)];
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 a0edc7c..1e52511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -149,7 +149,7 @@
private void reinflate() {
int index = mStatusBarWindow.indexOfChild(mBrightnessMirror);
mStatusBarWindow.removeView(mBrightnessMirror);
- mBrightnessMirror = (FrameLayout) LayoutInflater.from(mBrightnessMirror.getContext())
+ mBrightnessMirror = (FrameLayout) LayoutInflater.from(mStatusBarWindow.getContext())
.inflate(R.layout.brightness_mirror_container, mStatusBarWindow, false);
mToggleSliderController = setMirrorLayout();
mStatusBarWindow.addView(mBrightnessMirror, index);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 5e70d0d..d838a05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardVisibilityHelper;
@@ -56,7 +57,7 @@
* Manages the user switch on the Keyguard that is used for opening the QS user panel.
*/
@KeyguardUserSwitcherScope
-public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> {
+public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> {
private static final String TAG = "KeyguardQsUserSwitchController";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -76,6 +77,7 @@
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final KeyguardUserDetailAdapter mUserDetailAdapter;
private NotificationPanelViewController mNotificationPanelViewController;
+ private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
// State info for the user switch and keyguard
@@ -111,7 +113,7 @@
@Inject
public KeyguardQsUserSwitchController(
- UserAvatarView view,
+ FrameLayout view,
Context context,
@Main Resources resources,
ScreenLifecycle screenLifecycle,
@@ -143,6 +145,7 @@
protected void onInit() {
super.onInit();
if (DEBUG) Log.d(TAG, "onInit");
+ mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
@@ -150,11 +153,10 @@
}
};
- mView.setOnClickListener(v -> {
+ mUserAvatarView.setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
-
if (isListAnimating()) {
return;
}
@@ -163,7 +165,7 @@
openQsUserPanel();
});
- mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
@@ -237,12 +239,12 @@
R.string.accessibility_multi_user_switch_switcher);
}
- if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
- mView.setContentDescription(contentDescription);
+ if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) {
+ mUserAvatarView.setContentDescription(contentDescription);
}
int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
- mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
+ mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
}
Drawable getCurrentUserIcon() {
@@ -269,7 +271,7 @@
* Get the height of the keyguard user switcher view when closed.
*/
public int getUserIconHeight() {
- return mView.getHeight();
+ return mUserAvatarView.getHeight();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
index 20b66f0..cd8894c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -103,7 +103,7 @@
}
}
- if (animate) {
+ if (animate && userItemViews.length > 1) {
// AnimationUtils will immediately hide/show the first item in the array. Since the
// first view is the current user, we want to manage its visibility separately.
// Set first item to null so AnimationUtils ignores it.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index ef2ca98..eeea699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -23,6 +23,7 @@
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
@@ -223,9 +224,11 @@
void addAccessPointCallback(AccessPointCallback callback);
void removeAccessPointCallback(AccessPointCallback callback);
void scanForAccessPoints();
+ MergedCarrierEntry getMergedCarrierEntry();
int getIcon(WifiEntry ap);
boolean connect(WifiEntry ap);
boolean canConfigWifi();
+ boolean canConfigMobileData();
public interface AccessPointCallback {
void onAccessPointsChanged(List<WifiEntry> accessPoints);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c49de7a..af0d413 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -68,9 +68,12 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.qs.tiles.dialog.InternetDialogUtil;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -191,6 +194,8 @@
private boolean mUserSetup;
private boolean mSimDetected;
private boolean mForceCellularValidated;
+ private InternetDialogFactory mInternetDialogFactory;
+ private Handler mMainHandler;
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -221,7 +226,9 @@
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
FeatureFlags featureFlags,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Main Handler handler,
+ InternetDialogFactory internetDialogFactory) {
this(context, connectivityManager,
telephonyManager,
telephonyListenerManager,
@@ -242,6 +249,8 @@
featureFlags,
dumpManager);
mReceiverHandler.post(mRegisterListeners);
+ mMainHandler = handler;
+ mInternetDialogFactory = internetDialogFactory;
}
@VisibleForTesting
@@ -480,6 +489,9 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ if (InternetDialogUtil.isProviderModelEnabled(mContext)) {
+ filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
+ }
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
@@ -788,6 +800,10 @@
mConfig = Config.readConfig(mContext);
mReceiverHandler.post(this::handleConfigurationChanged);
break;
+ case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
+ mMainHandler.post(() -> mInternetDialogFactory.create(true,
+ mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi()));
+ break;
default:
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 22fd93e..2d47c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -71,7 +71,11 @@
if (mNextAlarm != null) {
pw.println(new Date(mNextAlarm.getTriggerTime()));
pw.print(" PendingIntentPkg=");
- pw.println(mNextAlarm.getShowIntent().getCreatorPackage());
+ if (mNextAlarm.getShowIntent() != null) {
+ pw.println(mNextAlarm.getShowIntent().getCreatorPackage());
+ } else {
+ pw.println("showIntent=null");
+ }
} else {
pw.println("null");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 84d7c05..5d7d480 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -77,7 +77,6 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -204,7 +203,7 @@
final int stroke = colorized ? mContext.getResources().getDimensionPixelSize(
R.dimen.remote_input_view_text_stroke) : 0;
if (colorized) {
- final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+ final boolean dark = Notification.Builder.isColorDark(backgroundColor);
final int foregroundColor = dark ? Color.WHITE : Color.BLACK;
final int inverseColor = dark ? Color.BLACK : Color.WHITE;
editBgColor = backgroundColor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index f258fb1..1158324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -23,6 +23,7 @@
int getRotationLockOrientation();
boolean isRotationLockAffordanceVisible();
boolean isRotationLocked();
+ boolean isCameraRotationEnabled();
void setRotationLocked(boolean locked);
void setRotationLockedAtAngle(boolean locked, int rotation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 53d68d0..c185928 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -18,11 +18,13 @@
import android.content.Context;
import android.os.UserHandle;
+import android.provider.Settings.Secure;
import androidx.annotation.NonNull;
import com.android.internal.view.RotationPolicy;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -32,20 +34,22 @@
@SysUISingleton
public final class RotationLockControllerImpl implements RotationLockController {
private final Context mContext;
+ private final SecureSettings mSecureSettings;
private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
new CopyOnWriteArrayList<RotationLockControllerCallback>();
private final RotationPolicy.RotationPolicyListener mRotationPolicyListener =
new RotationPolicy.RotationPolicyListener() {
- @Override
- public void onChange() {
- notifyChanged();
- }
- };
+ @Override
+ public void onChange() {
+ notifyChanged();
+ }
+ };
@Inject
- public RotationLockControllerImpl(Context context) {
+ public RotationLockControllerImpl(Context context, SecureSettings secureSettings) {
mContext = context;
+ mSecureSettings = secureSettings;
setListening(true);
}
@@ -68,11 +72,16 @@
return RotationPolicy.isRotationLocked(mContext);
}
+ public boolean isCameraRotationEnabled() {
+ return mSecureSettings.getIntForUser(Secure.CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT)
+ == 1;
+ }
+
public void setRotationLocked(boolean locked) {
RotationPolicy.setRotationLock(mContext, locked);
}
- public void setRotationLockedAtAngle(boolean locked, int rotation){
+ public void setRotationLockedAtAngle(boolean locked, int rotation) {
RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 41b1dd1..4e33529 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -628,7 +628,7 @@
mCurrentBackgroundColor = backgroundColor;
mCurrentColorized = colorized;
- final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+ final boolean dark = Notification.Builder.isColorDark(backgroundColor);
mCurrentTextColor = ContrastColorUtil.ensureTextContrast(
dark ? mDefaultTextColorDarkBg : mDefaultTextColor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
new file mode 100644
index 0000000..ae9d9ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.policy
+
+import android.content.Context
+import android.text.StaticLayout
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.R
+
+/**
+ * View for showing a date that can toggle between two different formats depending on size.
+ *
+ * If no pattern can fit, it will display empty.
+ *
+ * @see R.styleable.VariableDateView_longDatePattern
+ * @see R.styleable.VariableDateView_shortDatePattern
+ */
+class VariableDateView(context: Context, attrs: AttributeSet) : TextView(context, attrs) {
+
+ val longerPattern: String
+ val shorterPattern: String
+
+ init {
+ val a = context.theme.obtainStyledAttributes(
+ attrs,
+ R.styleable.VariableDateView,
+ 0, 0)
+ longerPattern = a.getString(R.styleable.VariableDateView_longDatePattern)
+ ?: context.getString(R.string.system_ui_date_pattern)
+ shorterPattern = a.getString(R.styleable.VariableDateView_shortDatePattern)
+ ?: context.getString(R.string.abbrev_month_day_no_year)
+
+ a.recycle()
+ }
+
+ /**
+ * Freeze the pattern switching
+ *
+ * Use during animations if the container will change its size but this view should not change
+ */
+ var freezeSwitching = false
+
+ private var onMeasureListener: OnMeasureListener? = null
+
+ fun onAttach(listener: OnMeasureListener?) {
+ onMeasureListener = listener
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val availableWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingStart - paddingEnd
+ if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED && !freezeSwitching) {
+ onMeasureListener?.onMeasureAction(availableWidth)
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+
+ fun getDesiredWidthForText(text: CharSequence): Float {
+ return StaticLayout.getDesiredWidth(text, paint)
+ }
+
+ interface OnMeasureListener {
+ fun onMeasureAction(availableWidth: Int)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
new file mode 100644
index 0000000..99d84c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2021 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.policy
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.icu.text.DateFormat
+import android.icu.text.DisplayContext
+import android.icu.util.Calendar
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dependency
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.ViewController
+import com.android.systemui.util.time.SystemClock
+import java.text.FieldPosition
+import java.text.ParsePosition
+import java.util.Date
+import java.util.Locale
+import javax.inject.Inject
+import javax.inject.Named
+
+@VisibleForTesting
+internal fun getTextForFormat(date: Date?, format: DateFormat): String {
+ return if (format === EMPTY_FORMAT) { // Check if same object
+ ""
+ } else format.format(date)
+}
+
+@VisibleForTesting
+internal fun getFormatFromPattern(pattern: String?): DateFormat {
+ if (TextUtils.equals(pattern, "")) {
+ return EMPTY_FORMAT
+ }
+ val l = Locale.getDefault()
+ val format = DateFormat.getInstanceForSkeleton(pattern, l)
+ format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
+ return format
+}
+
+private val EMPTY_FORMAT: DateFormat = object : DateFormat() {
+ override fun format(
+ cal: Calendar,
+ toAppendTo: StringBuffer,
+ fieldPosition: FieldPosition
+ ): StringBuffer? {
+ return null
+ }
+
+ override fun parse(text: String, cal: Calendar, pos: ParsePosition) {}
+}
+
+private const val DEBUG = false
+private const val TAG = "VariableDateViewController"
+
+class VariableDateViewController(
+ private val systemClock: SystemClock,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val timeTickHandler: Handler,
+ view: VariableDateView
+) : ViewController<VariableDateView>(view) {
+
+ private var dateFormat: DateFormat? = null
+ private var datePattern = view.longerPattern
+ set(value) {
+ if (field == value) return
+ field = value
+ dateFormat = null
+ if (isAttachedToWindow) {
+ post(::updateClock)
+ }
+ }
+ private var lastWidth = Integer.MAX_VALUE
+ private var lastText = ""
+ private var currentTime = Date()
+
+ // View class easy accessors
+ private val longerPattern: String
+ get() = mView.longerPattern
+ private val shorterPattern: String
+ get() = mView.shorterPattern
+ private fun post(block: () -> Unit) = mView.handler?.post(block)
+
+ private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ // If the handler is null, it means we received a broadcast while the view has not
+ // finished being attached or in the process of being detached.
+ // In that case, do not post anything.
+ val handler = mView.handler ?: return
+ val action = intent.action
+ if (
+ Intent.ACTION_TIME_TICK == action ||
+ Intent.ACTION_TIME_CHANGED == action ||
+ Intent.ACTION_TIMEZONE_CHANGED == action ||
+ Intent.ACTION_LOCALE_CHANGED == action
+ ) {
+ if (
+ Intent.ACTION_LOCALE_CHANGED == action ||
+ Intent.ACTION_TIMEZONE_CHANGED == action
+ ) {
+ // need to get a fresh date format
+ handler.post { dateFormat = null }
+ }
+ handler.post(::updateClock)
+ }
+ }
+ }
+
+ private val onMeasureListener = object : VariableDateView.OnMeasureListener {
+ override fun onMeasureAction(availableWidth: Int) {
+ if (availableWidth != lastWidth) {
+ // maybeChangeFormat will post if the pattern needs to change.
+ maybeChangeFormat(availableWidth)
+ lastWidth = availableWidth
+ }
+ }
+ }
+
+ override fun onViewAttached() {
+ val filter = IntentFilter().apply {
+ addAction(Intent.ACTION_TIME_TICK)
+ addAction(Intent.ACTION_TIME_CHANGED)
+ addAction(Intent.ACTION_TIMEZONE_CHANGED)
+ addAction(Intent.ACTION_LOCALE_CHANGED)
+ }
+
+ broadcastDispatcher.registerReceiver(intentReceiver, filter,
+ HandlerExecutor(timeTickHandler), UserHandle.SYSTEM)
+
+ post(::updateClock)
+ mView.onAttach(onMeasureListener)
+ }
+
+ override fun onViewDetached() {
+ dateFormat = null
+ mView.onAttach(null)
+ broadcastDispatcher.unregisterReceiver(intentReceiver)
+ }
+
+ private fun updateClock() {
+ if (dateFormat == null) {
+ dateFormat = getFormatFromPattern(datePattern)
+ }
+
+ currentTime.time = systemClock.currentTimeMillis()
+
+ val text = getTextForFormat(currentTime, dateFormat!!)
+ if (text != lastText) {
+ mView.setText(text)
+ lastText = text
+ }
+ }
+
+ private fun maybeChangeFormat(availableWidth: Int) {
+ if (mView.freezeSwitching ||
+ availableWidth > lastWidth && datePattern == longerPattern ||
+ availableWidth < lastWidth && datePattern == ""
+ ) {
+ // Nothing to do
+ return
+ }
+ if (DEBUG) Log.d(TAG, "Width changed. Maybe changing pattern")
+ // Start with longer pattern and see what fits
+ var text = getTextForFormat(currentTime, getFormatFromPattern(longerPattern))
+ var length = mView.getDesiredWidthForText(text)
+ if (length <= availableWidth) {
+ changePattern(longerPattern)
+ return
+ }
+
+ text = getTextForFormat(currentTime, getFormatFromPattern(shorterPattern))
+ length = mView.getDesiredWidthForText(text)
+ if (length <= availableWidth) {
+ changePattern(shorterPattern)
+ return
+ }
+
+ changePattern("")
+ }
+
+ private fun changePattern(newPattern: String) {
+ if (newPattern.equals(datePattern)) return
+ if (DEBUG) Log.d(TAG, "Changing pattern to $newPattern")
+ datePattern = newPattern
+ }
+
+ class Factory @Inject constructor(
+ private val systemClock: SystemClock,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
+ ) {
+ fun create(view: VariableDateView): VariableDateViewController {
+ return VariableDateViewController(
+ systemClock,
+ broadcastDispatcher,
+ handler,
+ view
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index c3b4fbe..e2d0bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -132,14 +132,18 @@
/* Target package for each overlay category. */
private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>();
private final OverlayManager mOverlayManager;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
+ private final Executor mMainExecutor;
private final String mLauncherPackage;
private final String mThemePickerPackage;
- public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor,
+ public ThemeOverlayApplier(OverlayManager overlayManager,
+ Executor bgExecutor,
+ Executor mainExecutor,
String launcherPackage, String themePickerPackage, DumpManager dumpManager) {
mOverlayManager = overlayManager;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
mLauncherPackage = launcherPackage;
mThemePickerPackage = themePickerPackage;
mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
@@ -170,12 +174,12 @@
* Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
* affect sysui will also be applied to the system user.
*/
- void applyCurrentUserOverlays(
+ public void applyCurrentUserOverlays(
Map<String, OverlayIdentifier> categoryToPackage,
FabricatedOverlay[] pendingCreation,
int currentUser,
Set<UserHandle> managedProfiles) {
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
// Disable all overlays that have not been specified in the user setting.
final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
index 98b4209e..bfa50bc 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
@@ -63,6 +64,8 @@
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ getWindow().addPrivateFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
Intent intent = getIntent();
mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index de5a363..14190fa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -23,8 +23,9 @@
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import javax.inject.Inject;
@@ -34,39 +35,54 @@
*/
@SysUISingleton
public class CarrierConfigTracker extends BroadcastReceiver {
- private final SparseArray<Boolean> mCallStrengthConfigs = new SparseArray<>();
- private final SparseArray<Boolean> mNoCallingConfigs = new SparseArray<>();
+ private final SparseBooleanArray mCallStrengthConfigs = new SparseBooleanArray();
+ private final SparseBooleanArray mNoCallingConfigs = new SparseBooleanArray();
+ private final SparseBooleanArray mCarrierProvisionsWifiMergedNetworks =
+ new SparseBooleanArray();
private final CarrierConfigManager mCarrierConfigManager;
private boolean mDefaultCallStrengthConfigLoaded;
private boolean mDefaultCallStrengthConfig;
private boolean mDefaultNoCallingConfigLoaded;
private boolean mDefaultNoCallingConfig;
+ private boolean mDefaultCarrierProvisionsWifiMergedNetworksLoaded;
+ private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
@Inject
- public CarrierConfigTracker(Context context) {
+ public CarrierConfigTracker(Context context, BroadcastDispatcher broadcastDispatcher) {
mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
- context.registerReceiver(
+ broadcastDispatcher.registerReceiver(
this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
}
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction() == CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) {
- int subId = intent.getIntExtra(
- CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- return;
- }
- PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
- if (b != null) {
- boolean hideNoCallingConfig = b.getBoolean(
- CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL);
- boolean displayCallStrengthIcon = b.getBoolean(
- CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL);
- mCallStrengthConfigs.put(subId, displayCallStrengthIcon);
- mNoCallingConfigs.put(subId, hideNoCallingConfig);
- }
+ if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ return;
+ }
+
+ final int subId = intent.getIntExtra(
+ CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ return;
+ }
+
+ final PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+ if (config == null) {
+ return;
+ }
+
+ synchronized (mCallStrengthConfigs) {
+ mCallStrengthConfigs.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL));
+ }
+ synchronized (mNoCallingConfigs) {
+ mNoCallingConfigs.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL));
+ }
+ synchronized (mCarrierProvisionsWifiMergedNetworks) {
+ mCarrierProvisionsWifiMergedNetworks.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL));
}
}
@@ -74,8 +90,10 @@
* Returns the KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL value for the given subId.
*/
public boolean getCallStrengthConfig(int subId) {
- if (mCallStrengthConfigs.indexOfKey(subId) >= 0) {
- return mCallStrengthConfigs.get(subId);
+ synchronized (mCallStrengthConfigs) {
+ if (mCallStrengthConfigs.indexOfKey(subId) >= 0) {
+ return mCallStrengthConfigs.get(subId);
+ }
}
if (!mDefaultCallStrengthConfigLoaded) {
mDefaultCallStrengthConfig =
@@ -90,8 +108,10 @@
* Returns the KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL value for the given subId.
*/
public boolean getNoCallingConfig(int subId) {
- if (mNoCallingConfigs.indexOfKey(subId) >= 0) {
- return mNoCallingConfigs.get(subId);
+ synchronized (mNoCallingConfigs) {
+ if (mNoCallingConfigs.indexOfKey(subId) >= 0) {
+ return mNoCallingConfigs.get(subId);
+ }
}
if (!mDefaultNoCallingConfigLoaded) {
mDefaultNoCallingConfig =
@@ -101,4 +121,22 @@
}
return mDefaultNoCallingConfig;
}
+
+ /**
+ * Returns the KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL value for the given subId.
+ */
+ public boolean getCarrierProvisionsWifiMergedNetworksBool(int subId) {
+ synchronized (mCarrierProvisionsWifiMergedNetworks) {
+ if (mCarrierProvisionsWifiMergedNetworks.indexOfKey(subId) >= 0) {
+ return mCarrierProvisionsWifiMergedNetworks.get(subId);
+ }
+ }
+ if (!mDefaultCarrierProvisionsWifiMergedNetworksLoaded) {
+ mDefaultCarrierProvisionsWifiMergedNetworks =
+ CarrierConfigManager.getDefaultConfig().getBoolean(
+ CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL);
+ mDefaultCarrierProvisionsWifiMergedNetworksLoaded = true;
+ }
+ return mDefaultCarrierProvisionsWifiMergedNetworks;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 90e022a5..bd11039 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -87,15 +87,23 @@
&& (mLastPrimaryEvent == null
|| !mLastPrimaryEvent.getBelow()
|| !event.getBelow())) {
- mSecondaryThresholdSensor.pause();
+ chooseSensor();
if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
// Only check the secondary as long as the primary thinks we're near.
- mCancelSecondaryRunnable = null;
+ if (mCancelSecondaryRunnable != null) {
+ mCancelSecondaryRunnable.run();
+ mCancelSecondaryRunnable = null;
+ }
return;
} else {
// Check this sensor again in a moment.
- mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(
- mSecondaryThresholdSensor::resume, SECONDARY_PING_INTERVAL_MS);
+ mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
+ // This is safe because we know that mSecondaryThresholdSensor
+ // is loaded, otherwise we wouldn't be here.
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.resume();
+ },
+ SECONDARY_PING_INTERVAL_MS);
}
}
logDebug("Secondary sensor event: " + event.getBelow() + ".");
@@ -159,12 +167,8 @@
* of what is reported by the primary sensor.
*/
public void setSecondarySafe(boolean safe) {
- mSecondarySafe = safe;
- if (!mSecondarySafe) {
- mSecondaryThresholdSensor.pause();
- } else {
- mSecondaryThresholdSensor.resume();
- }
+ mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
+ chooseSensor();
}
/**
@@ -209,16 +213,30 @@
return;
}
if (!mInitializedListeners) {
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.pause();
mPrimaryThresholdSensor.register(mPrimaryEventListener);
- if (!mSecondarySafe) {
- mSecondaryThresholdSensor.pause();
- }
mSecondaryThresholdSensor.register(mSecondaryEventListener);
mInitializedListeners = true;
}
logDebug("Registering sensor listener");
- mPrimaryThresholdSensor.resume();
+
mRegistered = true;
+ chooseSensor();
+ }
+
+ private void chooseSensor() {
+ mExecution.assertIsMainThread();
+ if (!mRegistered || mPaused || mListeners.isEmpty()) {
+ return;
+ }
+ if (mSecondarySafe) {
+ mSecondaryThresholdSensor.resume();
+ mPrimaryThresholdSensor.pause();
+ } else {
+ mPrimaryThresholdSensor.resume();
+ mSecondaryThresholdSensor.pause();
+ }
}
/**
@@ -312,7 +330,7 @@
}
if (!mSecondarySafe && !event.getBelow()) {
- mSecondaryThresholdSensor.pause();
+ chooseSensor();
}
mLastEvent = event;
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 2b4b49b..9917777 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -116,7 +116,7 @@
if (toolbar != null) {
setActionBar(toolbar);
}
- setTitle("");
+ getActionBar().setDisplayShowTitleEnabled(false);
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeAsUpIndicator(getHomeIndicatorDrawable());
getActionBar().setHomeActionContentDescription(R.string.accessibility_desc_close);
@@ -220,6 +220,12 @@
}
@Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.wallet_activity_options_menu, menu);
return super.onCreateOptionsMenu(menu);
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 5f1fe92..763a5cb 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -93,6 +93,13 @@
<activity android:name="com.android.systemui.screenshot.RecyclerViewActivity"
android:exported="false" />
+ <!-- started from UsbDeviceSettingsManager -->
+ <activity android:name=".usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
+ android:exported="false"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true" />
+
<provider
android:name="androidx.startup.InitializationProvider"
tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 06b0bb2..8077dea 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -119,6 +119,7 @@
when(mNotificationIcons.getLayoutParams()).thenReturn(
mock(RelativeLayout.LayoutParams.class));
when(mView.getContext()).thenReturn(getContext());
+ when(mView.getResources()).thenReturn(mResources);
when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
@@ -127,7 +128,6 @@
when(mLargeClockView.getContext()).thenReturn(getContext());
when(mView.isAttachedToWindow()).thenReturn(true);
- when(mResources.getString(anyInt())).thenReturn("h:mm");
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController = new KeyguardClockSwitchController(
mView,
@@ -142,7 +142,8 @@
mBypassController,
mSmartspaceController,
mKeyguardUnlockAnimationController,
- mSmartSpaceTransitionController
+ mSmartSpaceTransitionController,
+ mResources
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 39d5314..8dd5d6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -565,8 +565,9 @@
credentialAllowed,
true /* requireConfirmation */,
0 /* userId */,
- "testPackage",
0 /* operationId */,
+ "testPackage",
+ 1 /* requestId */,
BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT);
}
@@ -612,7 +613,7 @@
@Override
protected AuthDialog buildDialog(PromptInfo promptInfo,
boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
- String opPackageName, boolean skipIntro, long operationId,
+ String opPackageName, boolean skipIntro, long operationId, long requestId,
@BiometricManager.BiometricMultiSensorMode int multiSensorConfig) {
mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 1a39017..04ebee89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
+
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -55,7 +57,6 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -63,12 +64,14 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.time.SystemClock;
import org.junit.Before;
import org.junit.Rule;
@@ -120,8 +123,6 @@
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
@Mock
private FalsingManager mFalsingManager;
@@ -147,6 +148,10 @@
private Handler mHandler;
@Mock
private ConfigurationController mConfigurationController;
+ @Mock
+ private SystemClock mSystemClock;
+ @Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private FakeExecutor mFgExecutor;
@@ -186,6 +191,7 @@
when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view, null))
.thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD
when(mEnrollView.getContext()).thenReturn(mContext);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -216,7 +222,6 @@
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
- mKeyguardViewMediator,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
@@ -229,7 +234,9 @@
mKeyguardBypassController,
mDisplayManager,
mHandler,
- mConfigurationController);
+ mConfigurationController,
+ mSystemClock,
+ mUnlockedScreenOffAnimationController);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -413,6 +420,21 @@
}
@Test
+ public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
+ // GIVEN overlay was showing and the udfps bouncer is showing
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+ IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+
+ // WHEN the overlay is hidden
+ mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mFgExecutor.runAllReady();
+
+ // THEN the udfps bouncer is reset
+ verify(mStatusBarKeyguardViewManager).resetAlternateAuth(eq(true));
+ }
+
+ @Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
@@ -564,5 +586,10 @@
eq(mUdfpsController.EFFECT_CLICK),
eq("udfps-onStart"),
eq(UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES));
+
+ // THEN make sure vibration attributes has so that it always will play the haptic,
+ // even in battery saver mode
+ assertEquals(USAGE_ASSISTANCE_ACCESSIBILITY,
+ UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES.getUsage());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0fbf9af..4b35de1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,9 +18,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
-
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -43,9 +43,11 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -88,7 +90,10 @@
@Mock
private ConfigurationController mConfigurationController;
@Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ @Mock
private UdfpsController mUdfpsController;
+ private FakeSystemClock mSystemClock = new FakeSystemClock();
private UdfpsKeyguardViewController mController;
@@ -119,12 +124,12 @@
mStatusBar,
mStatusBarKeyguardViewManager,
mKeyguardUpdateMonitor,
- mExecutor,
mDumpManager,
- mKeyguardViewMediator,
mLockscreenShadeTransitionController,
mConfigurationController,
+ mSystemClock,
mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
mUdfpsController);
}
@@ -299,6 +304,59 @@
}
@Test
+ public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer isn't showing
+ mAltAuthInterceptor.hideAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view
+ mController.onTouchOutsideView();
+
+ // THEN bouncer / alt auth methods are never called
+ verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+ }
+
+ @Test
+ public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer is showing
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view 200ms later (just within threshold)
+ mSystemClock.advanceTime(200);
+ mController.onTouchOutsideView();
+
+ // THEN bouncer / alt auth methods are never called because not enough time has passed
+ verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+ }
+
+ @Test
+ public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer is showing
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view 205ms later
+ mSystemClock.advanceTime(205);
+ mController.onTouchOutsideView();
+
+ // THEN show the bouncer and reset alt auth
+ verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
+ verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean());
+ }
+
+ @Test
public void testFadeInWithStatusBarExpansion() {
// GIVEN view is attached
mController.onViewAttached();
@@ -395,6 +453,8 @@
mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
}
+
+
private void captureKeyguardStateControllerCallback() {
verify(mKeyguardStateController).addCallback(
mKeyguardStateControllerCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index cb55efa..b8d465a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -18,6 +18,7 @@
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
@@ -29,6 +30,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,11 +44,11 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
-import android.view.Display;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -84,6 +86,8 @@
@Mock
DozeParameters mDozeParameters;
@Mock
+ DockManager mDockManager;
+ @Mock
private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
@@ -112,9 +116,8 @@
mSensor = fakeSensorManager.getFakeLightSensor();
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters,
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
mUnlockedScreenOffAnimationController);
- mScreen.onScreenState(Display.STATE_ON);
}
@Test
@@ -126,18 +129,9 @@
}
@Test
- public void testAod_usesLightSensor() {
- mScreen.onScreenState(Display.STATE_DOZE);
- waitForSensorManager();
-
- mSensor.sendSensorEvent(3);
-
- assertEquals(3, mServiceFake.screenBrightness);
- }
-
- @Test
public void testAod_usesDebugValue() throws Exception {
- mScreen.onScreenState(Display.STATE_DOZE);
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
waitForSensorManager();
Intent intent = new Intent(DozeScreenBrightness.ACTION_AOD_BRIGHTNESS);
@@ -160,10 +154,53 @@
}
@Test
+ public void doze_doesNotUseLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is NOT changed, it's set to the default brightness
+ assertNotSame(3, mServiceFake.screenBrightness);
+ assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void aod_usesLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is updated
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void docked_usesLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ mScreen.transitionTo(DOZE_AOD, DOZE_AOD_DOCKED);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is updated
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
public void testPausingAod_doesNotResetBrightness() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(1);
@@ -178,7 +215,7 @@
public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters,
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
mUnlockedScreenOffAnimationController);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
@@ -205,37 +242,22 @@
}
@Test
- public void testOnScreenStateSetBeforeTransition_stillRegistersSensor() {
- mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- mScreen.onScreenState(Display.STATE_DOZE);
- mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- waitForSensorManager();
-
- mSensor.sendSensorEvent(1);
-
- assertEquals(1, mServiceFake.screenBrightness);
- }
-
- @Test
public void testNullSensor() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters,
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
mUnlockedScreenOffAnimationController);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
- mScreen.onScreenState(Display.STATE_DOZE);
- mScreen.onScreenState(Display.STATE_OFF);
}
@Test
public void testNoBrightnessDeliveredAfterFinish() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
mScreen.transitionTo(DOZE_AOD, FINISH);
waitForSensorManager();
@@ -248,7 +270,6 @@
public void testNonPositiveBrightness_keepsPreviousBrightnessAndScrim() {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(1);
@@ -262,7 +283,6 @@
public void pausingAod_unblanksAfterSensor() {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
@@ -274,7 +294,6 @@
reset(mDozeHost);
mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
verify(mDozeHost).setAodDimmingScrim(eq(0f));
@@ -284,7 +303,6 @@
public void pausingAod_unblanksIfSensorWasAlwaysReady() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 0c94f09..5c4c27c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -88,7 +88,7 @@
private FakeSettings mFakeSettings = new FakeSettings();
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
- private DozeSensors mDozeSensors;
+ private TestableDozeSensors mDozeSensors;
private TriggerSensor mSensorTap;
@Before
@@ -170,6 +170,94 @@
assertTrue(mSensorTap.mRequested);
}
+ @Test
+ public void testDozeSensorSetListening() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled */ true,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN we want to listen for the trigger sensor
+ triggerSensor.setListening(true);
+
+ // THEN the sensor is registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testDozeSensorSettingDisabled() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ false,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN setListening is called
+ triggerSensor.setListening(true);
+
+ // THEN the sensor is not registered
+ assertFalse(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testDozeSensorIgnoreSetting() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor that's
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ false,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // GIVEN sensor is listening
+ triggerSensor.setListening(true);
+
+ // WHEN ignoreSetting is called
+ triggerSensor.ignoreSetting(true);
+
+ // THEN the sensor is registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testUpdateListeningAfterAlreadyRegistered() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ true,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN setListening is called AND updateListening is called
+ triggerSensor.setListening(true);
+ triggerSensor.updateListening();
+
+ // THEN the sensor is still registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
private class TestableDozeSensors extends DozeSensors {
TestableDozeSensors() {
@@ -187,5 +275,17 @@
}
mSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
}
+
+ public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
+ boolean requiresTouchScreen) {
+ return new TriggerSensor(/* sensor */ sensor,
+ /* setting name */ "test_setting",
+ /* settingDefault */ settingEnabled,
+ /* configured */ true,
+ /* pulseReason*/ 0,
+ /* reportsTouchCoordinate*/ false,
+ requiresTouchScreen,
+ mDozeLog);
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 10997fa..9577c7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -83,6 +84,8 @@
private AuthController mAuthController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
@@ -114,7 +117,7 @@
mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
- mAuthController, mExecutor, mUiEventLogger);
+ mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
@@ -217,6 +220,32 @@
}
@Test
+ public void testPickupGesture() {
+ // GIVEN device is in doze (screen blank, but running doze sensors)
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN the pick up gesture is triggered and keyguard isn't occluded
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
+
+ // THEN wakeup
+ verify(mMachine).wakeUp();
+ }
+
+ @Test
+ public void testPickupGestureDroppedKeyguardOccluded() {
+ // GIVEN device is in doze (screen blank, but running doze sensors)
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN the pick up gesture is triggered and keyguard IS occluded
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
+
+ // THEN never wakeup
+ verify(mMachine, never()).wakeUp();
+ }
+
+ @Test
public void testOnSensor_Fingerprint() {
// GIVEN dozing state
when(mMachine.getState()).thenReturn(DOZE_AOD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 578c2d9..509ef82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -51,6 +52,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -112,6 +114,7 @@
@Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
@Mock private StatusBar mStatusBar;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private TestableLooper mTestableLooper;
@@ -156,7 +159,8 @@
mSysUiState,
mHandler,
mPackageManager,
- mStatusBar
+ mStatusBar,
+ mKeyguardUpdateMonitor
);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
@@ -422,4 +426,31 @@
restartAction.onLongPress();
verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
}
+
+ @Test
+ public void testOnLockScreen_disableSmartLock() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ doReturn(false).when(mStatusBar).isKeyguardShowing();
+ String[] actions = {
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ // When entering power menu from lockscreen, with smart lock enabled
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ mGlobalActionsDialogLite.showOrHideDialog(true, true);
+
+ // Then smart lock will be disabled
+ verify(mLockPatternUtils).requireCredentialEntry(eq(user));
+
+ // hide dialog again
+ mGlobalActionsDialogLite.showOrHideDialog(true, true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 2fa67cc..338bb30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -56,6 +56,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -126,6 +127,7 @@
@Mock private PackageManager mPackageManager;
@Mock private SecureSettings mSecureSettings;
@Mock private StatusBar mStatusBar;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private TestableLooper mTestableLooper;
@@ -169,7 +171,8 @@
mSysUiState,
mHandler,
mPackageManager,
- mStatusBar
+ mStatusBar,
+ mKeyguardUpdateMonitor
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
new file mode 100644
index 0000000..df11284
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 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.keyguard;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.AnimatableClockController;
+import com.android.keyguard.AnimatableClockView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.Utils;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AnimatableClockControllerTest extends SysuiTestCase {
+ @Mock
+ private AnimatableClockView mClockView;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private BatteryController mBatteryController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private KeyguardBypassController mBypassController;
+ @Mock
+ private Resources mResources;
+
+ private MockitoSession mStaticMockSession;
+ private AnimatableClockController mAnimatableClockController;
+
+ // Capture listeners so that they can be used to send events
+ @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+ private View.OnAttachStateChangeListener mAttachListener;
+
+ @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
+ private StatusBarStateController.StateListener mStatusBarStateCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession = mockitoSession()
+ .mockStatic(Utils.class)
+ .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
+ .startMocking();
+ when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
+
+ mAnimatableClockController = new AnimatableClockController(
+ mClockView,
+ mStatusBarStateController,
+ mBroadcastDispatcher,
+ mBatteryController,
+ mKeyguardUpdateMonitor,
+ mBypassController,
+ mResources
+ );
+ mAnimatableClockController.init();
+ captureAttachListener();
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testOnAttachedUpdatesDozeStateToTrue() {
+ // GIVEN dozing
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
+
+ // WHEN the clock view gets attached
+ mAttachListener.onViewAttachedToWindow(mClockView);
+
+ // THEN the clock controller updated its dozing state to true
+ assertTrue(mAnimatableClockController.isDozing());
+ }
+
+ @Test
+ public void testOnAttachedUpdatesDozeStateToFalse() {
+ // GIVEN not dozing
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+
+ // WHEN the clock view gets attached
+ mAttachListener.onViewAttachedToWindow(mClockView);
+
+ // THEN the clock controller updated its dozing state to false
+ assertFalse(mAnimatableClockController.isDozing());
+ }
+
+ private void captureAttachListener() {
+ verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
+ mAttachListener = mAttachCaptor.getValue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index ad08780..31d70f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -67,7 +67,7 @@
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
@SmallTest
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
@@ -126,7 +126,6 @@
mUnlockedScreenOffAnimationController,
() -> mNotificationShadeDepthController);
mViewMediator.start();
- mViewMediator.onSystemReady();
}
@Test
@@ -165,8 +164,10 @@
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway() {
// When showing and provisioned
+ mViewMediator.onSystemReady();
when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
mViewMediator.setShowingLocked(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 9c3016c..9356c54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -31,6 +31,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
+import android.util.Pair;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -119,26 +120,15 @@
mAccessibilityManager,
mConfigurationController,
mDelayableExecutor,
- mVibrator
+ mVibrator,
+ mAuthRippleController
);
}
@Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
- final PointF udfpsLocation = new PointF(50, 75);
- final int radius = 33;
- final FingerprintSensorPropertiesInternal fpProps =
- new FingerprintSensorPropertiesInternal(
- /* sensorId */ 0,
- /* strength */ 0,
- /* max enrollments per user */ 5,
- /* component info */ new ArrayList<>(),
- /* sensorType */ 3,
- /* resetLockoutRequiresHwToken */ false,
- (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
- when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
- when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+ Pair<Integer, PointF> udfps = setupUdfps();
// WHEN lock icon view controller is initialized and attached
mLockIconViewController.init();
@@ -146,8 +136,8 @@
mAttachListener.onViewAttachedToWindow(mLockIconView);
// THEN lock icon view location is updated with the same coordinates as fpProps
- verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
- assertEquals(udfpsLocation, mPointCaptor.getValue());
+ verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
+ assertEquals(udfps.second, mPointCaptor.getValue());
}
@Test
@@ -161,6 +151,47 @@
// GIVEN fp sensor location is available post-atttached
captureAuthControllerCallback();
+ Pair<Integer, PointF> udfps = setupUdfps();
+
+ // WHEN all authenticators are registered
+ mAuthControllerCallback.onAllAuthenticatorsRegistered();
+
+ // THEN lock icon view location is updated with the same coordinates as fpProps
+ verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
+ assertEquals(udfps.second, mPointCaptor.getValue());
+ }
+
+ @Test
+ public void testLockIconViewBackgroundEnabledWhenUdfpsIsAvailable() {
+ // GIVEN Udpfs sensor location is available
+ setupUdfps();
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN the lock icon view background should be enabled
+ verify(mLockIconView).setUseBackground(true);
+ }
+
+ @Test
+ public void testLockIconViewBackgroundDisabledWhenUdfpsIsUnavailable() {
+ // GIVEN Udfps sensor location is not available
+ when(mAuthController.getUdfpsSensorLocation()).thenReturn(null);
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN the lock icon view background should be disabled
+ verify(mLockIconView).setUseBackground(false);
+ }
+
+ private Pair<Integer, PointF> setupUdfps() {
final PointF udfpsLocation = new PointF(50, 75);
final int radius = 33;
final FingerprintSensorPropertiesInternal fpProps =
@@ -175,12 +206,7 @@
when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
- // WHEN all authenticators are registered
- mAuthControllerCallback.onAllAuthenticatorsRegistered();
-
- // THEN lock icon view location is updated with the same coordinates as fpProps
- verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
- assertEquals(udfpsLocation, mPointCaptor.getValue());
+ return new Pair(radius, udfpsLocation);
}
private void captureAuthControllerCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index b129fdd..42629f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -37,6 +37,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
@@ -101,7 +102,6 @@
private lateinit var seamless: ViewGroup
private lateinit var seamlessIcon: ImageView
private lateinit var seamlessText: TextView
- private lateinit var seamlessFallback: ImageView
private lateinit var seekBar: SeekBar
private lateinit var elapsedTimeView: TextView
private lateinit var totalTimeView: TextView
@@ -154,8 +154,6 @@
whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
seamlessText = TextView(context)
whenever(holder.seamlessText).thenReturn(seamlessText)
- seamlessFallback = ImageView(context)
- whenever(holder.seamlessFallback).thenReturn(seamlessFallback)
seekBar = SeekBar(context)
whenever(holder.seekBar).thenReturn(seekBar)
elapsedTimeView = TextView(context)
@@ -239,21 +237,19 @@
@Test
fun bindDisabledDevice() {
seamless.id = 1
- seamlessFallback.id = 2
+ val fallbackString = context.getString(R.string.media_seamless_other_device)
player.attachPlayer(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
player.bindPlayer(state, PACKAGE)
- verify(expandedSet).setVisibility(seamless.id, View.GONE)
- verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
- verify(collapsedSet).setVisibility(seamless.id, View.GONE)
- verify(collapsedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
+ assertThat(seamless.isEnabled()).isFalse()
+ assertThat(seamlessText.getText()).isEqualTo(fallbackString)
+ assertThat(seamless.contentDescription).isEqualTo(fallbackString)
}
@Test
fun bindNullDevice() {
- val fallbackString = context.getResources().getString(
- com.android.internal.R.string.ext_media_seamless_action)
+ val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
player.attachPlayer(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index d879186..28aed20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -86,7 +86,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- mediaDataFilter = MediaDataFilter(broadcastDispatcher, mediaResumeListener,
+ mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener,
lockscreenUserManager, executor, clock)
mediaDataFilter.mediaDataManager = mediaDataManager
mediaDataFilter.addListener(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index ba6dfd3..47c5545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -4,6 +4,7 @@
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceTarget
+import android.content.Intent
import android.graphics.Bitmap
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -51,6 +52,7 @@
private const val SESSION_ARTIST = "artist"
private const val SESSION_TITLE = "title"
private const val USER_ID = 0
+private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -83,6 +85,7 @@
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
+ @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
lateinit var mediaDataManager: MediaDataManager
lateinit var mediaNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -146,8 +149,12 @@
// treat mediaSessionBasedFilter as a listener for testing.
listener = mediaSessionBasedFilter
- val recommendationExtras = Bundle()
- recommendationExtras.putString("package_name", PACKAGE_NAME)
+ val recommendationExtras = Bundle().apply {
+ putString("package_name", PACKAGE_NAME)
+ putParcelable("dismiss_intent", DISMISS_INTENT)
+ }
+ whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
+ whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras)
whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
@@ -163,7 +170,7 @@
}
@Test
- fun testSetTimedOut_deactivatesMedia() {
+ fun testSetTimedOut_active_deactivatesMedia() {
val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
@@ -176,6 +183,25 @@
}
@Test
+ fun testSetTimedOut_resume_dismissesMedia() {
+ // WHEN resume controls are present, and time out
+ val desc = MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
+ APP_NAME, pendingIntent, PACKAGE_NAME)
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
+
+ // THEN it is removed and listeners are informed
+ foregroundExecutor.advanceClockToLast()
+ foregroundExecutor.runAllReady()
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ }
+
+ @Test
fun testLoadsMetadataOnBackground() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.numPending()).isEqualTo(1)
@@ -361,7 +387,8 @@
verify(listener).onSmartspaceMediaDataLoaded(
eq(KEY_MEDIA_SMARTSPACE),
eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */,
- PACKAGE_NAME, null, listOf(mediaRecommendationItem), 0)),
+ PACKAGE_NAME, mediaSmartspaceBaseAction, listOf(mediaRecommendationItem),
+ DISMISS_INTENT, 0)),
eq(false))
}
@@ -372,7 +399,8 @@
verify(listener).onSmartspaceMediaDataLoaded(
eq(KEY_MEDIA_SMARTSPACE),
eq(EMPTY_SMARTSPACE_MEDIA_DATA
- .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, isValid = false)),
+ .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true,
+ isValid = false, dismissIntent = DISMISS_INTENT)),
eq(false))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 150f4545..359746b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -91,10 +91,12 @@
@Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
@Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
+ @Captor lateinit var componentCaptor: ArgumentCaptor<String>
private lateinit var executor: FakeExecutor
private lateinit var data: MediaData
private lateinit var resumeListener: MediaResumeListener
+ private val clock = FakeSystemClock()
private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
@@ -122,9 +124,9 @@
whenever(mockContext.packageManager).thenReturn(context.packageManager)
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
- executor = FakeExecutor(FakeSystemClock())
+ executor = FakeExecutor(clock)
resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
- tunerService, resumeBrowserFactory, dumpManager)
+ tunerService, resumeBrowserFactory, dumpManager, clock)
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
@@ -163,7 +165,7 @@
// When listener is created, we do NOT register a user change listener
val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
- resumeBrowserFactory, dumpManager)
+ resumeBrowserFactory, dumpManager, clock)
listener.setManager(mediaDataManager)
verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
any(), any(), any())
@@ -328,4 +330,109 @@
// Then we call restart
verify(resumeBrowser).restart()
}
+
+ @Test
+ fun testOnUserUnlock_missingTime_saves() {
+ val currentTime = clock.currentTimeMillis()
+
+ // When resume components without a last played time are loaded
+ testOnUserUnlock_loadsTracks()
+
+ // Then we save an update with the current time
+ verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
+ componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
+ ?.dropLastWhile { it.isEmpty() }.forEach {
+ val result = it.split("/")
+ assertThat(result.size).isEqualTo(3)
+ assertThat(result[2].toLong()).isEqualTo(currentTime)
+ }
+ verify(sharedPrefsEditor, times(1)).apply()
+ }
+
+ @Test
+ fun testLoadComponents_recentlyPlayed_adds() {
+ // Set up browser to return successfully
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ whenever(resumeBrowser.token).thenReturn(token)
+ whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+ whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+ }
+
+ // Set up shared preferences to have a component with a recent lastplayed time
+ val lastPlayed = clock.currentTimeMillis()
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When we load a component that was played recently
+ val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+ resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+
+ // We add its resume controls
+ verify(resumeBrowser, times(1)).findRecentMedia()
+ verify(mediaDataManager, times(1)).addResumptionControls(anyInt(),
+ any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+ }
+
+ @Test
+ fun testLoadComponents_old_ignores() {
+ // Set up shared preferences to have a component with an old lastplayed time
+ val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When we load a component that is not recent
+ val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+ resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+
+ // We do not try to add resume controls
+ verify(resumeBrowser, times(0)).findRecentMedia()
+ verify(mediaDataManager, times(0)).addResumptionControls(anyInt(),
+ any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testOnLoad_hasService_updatesLastPlayed() {
+ // Set up browser to return successfully
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ whenever(resumeBrowser.token).thenReturn(token)
+ whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+ whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+ }
+
+ // Set up shared preferences to have a component with a lastplayed time
+ val currentTime = clock.currentTimeMillis()
+ val lastPlayed = currentTime - 1000
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When media data is loaded that has not been checked yet, and does have a MBS
+ val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
+ resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+
+ // Then we store the new lastPlayed time
+ verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
+ componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
+ ?.dropLastWhile { it.isEmpty() }.forEach {
+ val result = it.split("/")
+ assertThat(result.size).isEqualTo(3)
+ assertThat(result[2].toLong()).isEqualTo(currentTime)
+ }
+ verify(sharedPrefsEditor, times(1)).apply()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 0a573cd..de2235d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -71,6 +71,7 @@
private lateinit var playbackBuilder: PlaybackState.Builder
private lateinit var session: MediaSession
private lateinit var mediaData: MediaData
+ private lateinit var resumeData: MediaData
private lateinit var mediaTimeoutListener: MediaTimeoutListener
@Before
@@ -97,6 +98,10 @@
mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
device = null, active = true, resumeAction = null)
+
+ resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+ emptyList(), emptyList(), PACKAGE, null, clickIntent = null,
+ device = null, active = false, resumeAction = null, resumption = true)
}
@Test
@@ -120,6 +125,7 @@
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
assertThat(executor.numPending()).isEqualTo(1)
verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
}
@Test
@@ -188,6 +194,7 @@
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
.setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
assertThat(executor.numPending()).isEqualTo(1)
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
}
@Test
@@ -229,7 +236,7 @@
}
@Test
- fun testOnSessionDestroyed_clearsTimeout() {
+ fun testOnSessionDestroyed_active_clearsTimeout() {
// GIVEN media that is paused
val mediaPaused = mediaData.copy(isPlaying = false)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPaused)
@@ -247,7 +254,7 @@
@Test
fun testSessionDestroyed_thenRestarts_resetsTimeout() {
// Assuming we have previously destroyed the session
- testOnSessionDestroyed_clearsTimeout()
+ testOnSessionDestroyed_active_clearsTimeout()
// WHEN we get an update with media playing
val playingState = mock(android.media.session.PlaybackState::class.java)
@@ -264,4 +271,91 @@
}
verify(timeoutCallback).invoke(eq(KEY), eq(false))
}
+
+ @Test
+ fun testOnSessionDestroyed_resume_continuesTimeout() {
+ // GIVEN resume media with session info
+ val resumeWithSession = resumeData.copy(token = session.sessionToken)
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeWithSession)
+ verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // WHEN the session is destroyed
+ mediaCallbackCaptor.value.onSessionDestroyed()
+
+ // THEN the controller is unregistered, but the timeout is still scheduled
+ verify(mediaController).unregisterCallback(anyObject())
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_activeToResume_registersTimeout() {
+ // WHEN a regular media is loaded
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+
+ // AND it turns into a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData)
+
+ // THEN we register a timeout
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() {
+ // WHEN regular media is paused
+ val pausedState = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
+ .build()
+ `when`(mediaController.playbackState).thenReturn(pausedState)
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // AND it turns into a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData)
+
+ // THEN we update the timeout length
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_resumption_registersTimeout() {
+ // WHEN a resume media is loaded
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
+
+ // THEN we register a timeout
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_resumeToActive_updatesTimeout() {
+ // WHEN we have a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
+
+ // AND that media is resumed
+ val playingState = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
+ .build()
+ `when`(mediaController.playbackState).thenReturn(playingState)
+ mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
+
+ // THEN the timeout length is changed to a regular media control
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataRemoved_resume_timeoutCancelled() {
+ // WHEN we have a resume control
+ testOnMediaDataLoaded_resumption_registersTimeout()
+ // AND the media is removed
+ mediaTimeoutListener.onMediaDataRemoved(PACKAGE)
+
+ // THEN the timeout runnable is cancelled
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 03248f7..511848d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -22,11 +22,13 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.UserInfo
+import android.os.Process.SYSTEM_UID
import android.os.UserHandle
import android.permission.PermGroupUsage
import android.permission.PermissionManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
import com.android.systemui.plugins.ActivityStarter
@@ -53,6 +55,7 @@
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -96,6 +99,8 @@
private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
@Captor
private lateinit var intentCaptor: ArgumentCaptor<Intent>
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
private val backgroundExecutor = FakeExecutor(FakeSystemClock())
private val uiExecutor = FakeExecutor(FakeSystemClock())
@@ -136,6 +141,7 @@
privacyLogger,
keyguardStateController,
appOpsController,
+ uiEventLogger,
dialogProvider
)
}
@@ -550,6 +556,49 @@
verify(dialog, never()).dismiss()
}
+ @Test
+ fun testCallOnSecondaryUser() {
+ // Calls happen in
+ val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true)
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ `when`(userTracker.userProfiles).thenReturn(listOf(
+ UserInfo(ENT_USER_ID, "", 0)
+ ))
+
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ verify(dialog).show()
+ }
+
+ @Test
+ fun testStartActivityLogs() {
+ val usage = createMockPermGroupUsage()
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID)
+ verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+ USER_ID, TEST_PACKAGE_NAME)
+ }
+
+ @Test
+ fun testDismissedDialogLogs() {
+ val usage = createMockPermGroupUsage()
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor))
+
+ dialogDismissedCaptor.value.onDialogDismissed()
+
+ controller.dismissDialog()
+
+ verify(uiEventLogger, times(1)).log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+ }
+
private fun exhaustExecutors() {
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
index de7abf8..922c6b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
@@ -14,13 +14,19 @@
package com.android.systemui.qs;
-import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION;
import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.testing.AndroidTestingRunner;
@@ -28,13 +34,24 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -43,42 +60,38 @@
private static final int USER = 0;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private QSHost mQSHost;
+ @Mock
+ private DumpManager mDumpManager;
+ @Captor
+ private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor;
+
+ private Executor mBackgroundExecutor = Runnable::run; // Direct executor
private AutoAddTracker mAutoTracker;
+ private SecureSettings mSecureSettings;
@Before
public void setUp() {
- Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, "");
- }
+ MockitoAnnotations.initMocks(this);
- @Test
- public void testMigration() {
- Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
- Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true);
- mAutoTracker = new AutoAddTracker(mContext, USER);
+ mSecureSettings = new FakeSettings();
+
+ mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER);
+
+ mAutoTracker = createAutoAddTracker(USER);
mAutoTracker.initialize();
-
- assertTrue(mAutoTracker.isAdded(SAVER));
- assertTrue(mAutoTracker.isAdded(WORK));
- assertFalse(mAutoTracker.isAdded(INVERSION));
-
- // These keys have been removed; retrieving their values should always return the default.
- assertTrue(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true ));
- assertFalse(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, false));
- assertTrue(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, true));
- assertFalse(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, false));
-
- mAutoTracker.destroy();
}
@Test
public void testChangeFromBackup() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
- Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, SAVER);
- mAutoTracker.mObserver.onChange(false);
+ mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER);
assertTrue(mAutoTracker.isAdded(SAVER));
@@ -87,9 +100,6 @@
@Test
public void testSetAdded() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
mAutoTracker.setTileAdded(SAVER);
@@ -100,14 +110,12 @@
@Test
public void testPersist() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
mAutoTracker.setTileAdded(SAVER);
mAutoTracker.destroy();
- mAutoTracker = new AutoAddTracker(mContext, USER);
+ mAutoTracker = createAutoAddTracker(USER);
+ mAutoTracker.initialize();
assertTrue(mAutoTracker.isAdded(SAVER));
@@ -116,22 +124,158 @@
@Test
public void testIndependentUsers() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
mAutoTracker.setTileAdded(SAVER);
- mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+ mAutoTracker = createAutoAddTracker(USER + 1);
+ mAutoTracker.initialize();
assertFalse(mAutoTracker.isAdded(SAVER));
}
@Test
public void testChangeUser() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
mAutoTracker.setTileAdded(SAVER);
- mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+ mAutoTracker = createAutoAddTracker(USER + 1);
mAutoTracker.changeUser(UserHandle.of(USER));
assertTrue(mAutoTracker.isAdded(SAVER));
}
+
+ @Test
+ public void testBroadcastReceiverRegistered() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)));
+
+ assertTrue(
+ mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
+ }
+
+ @Test
+ public void testBroadcastReceiverChangesWithUser() {
+ mAutoTracker.changeUser(UserHandle.of(USER + 1));
+
+ InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
+ inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
+ inOrder.verify(mBroadcastDispatcher)
+ .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,work,internet,cast";
+ Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, don't remove any current tiles
+ verify(mQSHost, never()).removeTiles(any());
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent =
+ makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent =
+ makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+
+ String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER);
+ assertEquals(2, setting.split(",").length);
+ assertTrue(setting.contains("work"));
+ assertTrue(setting.contains("inversion"));
+ }
+
+
+ private Intent makeRestoreIntent(
+ String settingName, String previousValue, String restoredValue) {
+ Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED);
+ intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName);
+ intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue);
+ intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue);
+ return intent;
+ }
+
+ private AutoAddTracker createAutoAddTracker(int user) {
+ // Null handler wil dispatch sync.
+ return new AutoAddTracker(
+ mSecureSettings,
+ mBroadcastDispatcher,
+ mQSHost,
+ mDumpManager,
+ null,
+ mBackgroundExecutor,
+ user
+ );
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65e5f97..faef870 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -98,11 +99,11 @@
Resources mResources;
@Mock
Configuration mConfiguration;
+ @Mock
+ Runnable mHorizontalLayoutListener;
private QSPanelControllerBase<QSPanel> mController;
-
-
/** Implementation needed to ensure we have a reflectively-available class name. */
private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
@@ -242,18 +243,44 @@
when(mMediaHost.getVisible()).thenReturn(true);
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isFalse();
}
+
+ @Test
+ public void testChangeConfiguration_shouldUseHorizontalLayout() {
+ when(mMediaHost.getVisible()).thenReturn(true);
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+
+ // When device is rotated to landscape
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ verify(mHorizontalLayoutListener).run(); // not invoked
+
+ // When it is rotated back to portrait
+ mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes back
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener, times(2)).run();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index bf6c981..35ebacb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -23,6 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -98,6 +100,10 @@
FalsingManagerFake mFalsingManager = new FalsingManagerFake();
@Mock
FeatureFlags mFeatureFlags;
+ @Mock
+ Resources mResources;
+ @Mock
+ Configuration mConfiguration;
private QSPanelController mController;
@@ -109,6 +115,8 @@
when(mQSPanel.getDumpableTag()).thenReturn("QSPanel");
when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout);
when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
+ when(mQSPanel.getResources()).thenReturn(mResources);
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
when(mToggleSliderViewControllerFactory.create(any(), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 4cbad5f..0b67c9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -18,14 +18,11 @@
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.TestCase.assertFalse;
-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.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -68,12 +65,12 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
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;
@@ -121,7 +118,6 @@
private UiEventLogger mUiEventLogger;
@Mock
private UserTracker mUserTracker;
- @Mock
private SecureSettings mSecureSettings;
@Mock
private CustomTileStatePersister mCustomTileStatePersister;
@@ -137,14 +133,16 @@
MockitoAnnotations.initMocks(this);
mLooper = TestableLooper.get(this);
mHandler = new Handler(mLooper.getLooper());
+
+ mSecureSettings = new FakeSettings();
+ mSecureSettings.putStringForUser(
+ QSTileHost.TILES_SETTING, "", "", false, mUserTracker.getUserId(), false);
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
mSecureSettings, mCustomTileStatePersister, mFeatureFlags);
setUpTileFactory();
when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
- when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt()))
- .thenReturn("");
}
private void setUpTileFactory() {
@@ -416,6 +414,16 @@
.removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
}
+ @Test
+ public void testRemoveTiles() {
+ List<String> tiles = List.of("spec1", "spec2", "spec3");
+ mQSTileHost.saveTilesToSettings(tiles);
+
+ mQSTileHost.removeTiles(List.of("spec1", "spec2"));
+
+ assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
+ }
+
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
QSFactory defaultFactory, Handler mainHandler, Looper bgLooper,
@@ -442,14 +450,11 @@
@Override
void saveTilesToSettings(List<String> tileSpecs) {
super.saveTilesToSettings(tileSpecs);
-
- ArgumentCaptor<String> specs = ArgumentCaptor.forClass(String.class);
- verify(mSecureSettings, atLeastOnce()).putStringForUser(eq(QSTileHost.TILES_SETTING),
- specs.capture(), isNull(), eq(false), anyInt(), eq(true));
-
// After tiles are changed, make sure to call onTuningChanged with the new setting if it
// changed
- onTuningChanged(TILES_SETTING, specs.getValue());
+ String specs = mSecureSettings.getStringForUser(
+ QSTileHost.TILES_SETTING, mUserTracker.getUserId());
+ onTuningChanged(TILES_SETTING, specs);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 35360bd..8b7e20e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -36,6 +36,8 @@
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.Clock
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -87,8 +89,14 @@
@Mock
private lateinit var privacyDialogController: PrivacyDialogController
@Mock
+ private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+ @Mock
+ private lateinit var variableDateViewController: VariableDateViewController
+ @Mock
private lateinit var clock: Clock
@Mock
+ private lateinit var variableDateView: VariableDateView
+ @Mock
private lateinit var mockView: View
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var context: Context
@@ -109,6 +117,8 @@
stubViews()
`when`(iconContainer.context).thenReturn(context)
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+ `when`(variableDateViewControllerFactory.create(any()))
+ .thenReturn(variableDateViewController)
`when`(view.resources).thenReturn(mContext.resources)
`when`(view.isAttachedToWindow).thenReturn(true)
`when`(view.context).thenReturn(context)
@@ -133,7 +143,8 @@
colorExtractor,
privacyDialogController,
qsExpansionPathInterpolator,
- featureFlags
+ featureFlags,
+ variableDateViewControllerFactory
)
}
@@ -274,6 +285,8 @@
`when`(view.findViewById<StatusIconContainer>(R.id.statusIcons)).thenReturn(iconContainer)
`when`(view.findViewById<OngoingPrivacyChip>(R.id.privacy_chip)).thenReturn(privacyChip)
`when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock)
+ `when`(view.requireViewById<VariableDateView>(R.id.date)).thenReturn(variableDateView)
+ `when`(view.requireViewById<VariableDateView>(R.id.date_clock)).thenReturn(variableDateView)
}
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 63ebe92..23e5168 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -74,6 +75,24 @@
}
@Test
+ public void testMutateIconDrawable() {
+ SlashImageView iv = mock(SlashImageView.class);
+ Drawable originalDrawable = mock(Drawable.class);
+ Drawable otherDrawable = mock(Drawable.class);
+ State s = new State();
+ s.icon = mock(Icon.class);
+ when(s.icon.getInvisibleDrawable(eq(mContext))).thenReturn(originalDrawable);
+ when(s.icon.getDrawable(eq(mContext))).thenReturn(originalDrawable);
+ when(iv.isShown()).thenReturn(true);
+ when(originalDrawable.getConstantState()).thenReturn(fakeConstantState(otherDrawable));
+
+
+ mIconView.updateIcon(iv, s, /* allowAnimations= */true);
+
+ verify(iv).setState(any(), eq(otherDrawable));
+ }
+
+ @Test
public void testNoFirstFade() {
ImageView iv = mock(ImageView.class);
State s = new State();
@@ -104,4 +123,18 @@
public void testIconNotSet_toString() {
assertFalse(mIconView.toString().contains("lastIcon"));
}
+
+ private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
+ return new Drawable.ConstantState() {
+ @Override
+ public Drawable newDrawable() {
+ return otherDrawable;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return 1;
+ }
+ };
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
index 5e2d8fd..b4a66297 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
@@ -3,6 +3,7 @@
import android.app.AlarmManager
import android.app.PendingIntent
import android.os.Handler
+import android.provider.AlarmClock
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -98,6 +99,11 @@
}
@Test
+ fun testDefaultIntentShowAlarms() {
+ assertThat(tile.defaultIntent.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+ }
+
+ @Test
fun testInactiveByDefault() {
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index d44a526..e939411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -17,6 +17,7 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -327,4 +328,77 @@
assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name));
}
+
+ @Test
+ public void testExpandView_wifiNotConnected() {
+ mCastTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_wifiEnabledNotCasting() {
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_casting_projection() {
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastController.CastDevice.STATE_CONNECTED;
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_connecting_projection() {
+ CastController.CastDevice connecting = new CastController.CastDevice();
+ connecting.state = CastDevice.STATE_CONNECTING;
+ connecting.name = "Test Casting Device";
+
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(connecting);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_casting_mediaRoute() {
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastDevice.STATE_CONNECTED;
+ device.tag = mock(MediaRouter.RouteInfo.class);
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_connecting_mediaRoute() {
+ CastController.CastDevice connecting = new CastController.CastDevice();
+ connecting.state = CastDevice.STATE_CONNECTING;
+ connecting.tag = mock(RouteInfo.class);
+ connecting.name = "Test Casting Device";
+
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(connecting);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
new file mode 100644
index 0000000..49ab777
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 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.qs.tiles;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
+
+import static junit.framework.TestCase.assertEquals;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.text.TextUtils;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class RotationLockTileTest extends SysuiTestCase {
+
+ private static final String PACKAGE_NAME = "package_name";
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private QSTileHost mHost;
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private QSLogger mQSLogger;
+ @Mock
+ private SensorPrivacyManager mPrivacyManager;
+ @Mock
+ private BatteryController mBatteryController;
+
+ private SecureSettings mSecureSettings;
+ private RotationLockController mController;
+ private TestableLooper mTestableLooper;
+ private RotationLockTile mLockTile;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTestableLooper = TestableLooper.get(this);
+
+ when(mHost.getContext()).thenReturn(mContext);
+ when(mHost.getUserContext()).thenReturn(mContext);
+
+ mSecureSettings = new FakeSettings();
+ mController = new RotationLockControllerImpl(mContext, mSecureSettings);
+
+ mLockTile = new RotationLockTile(
+ mHost,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mController,
+ mPrivacyManager,
+ mBatteryController,
+ mSecureSettings
+ );
+
+ mLockTile.initialize();
+
+ // We are not setting the mocks to listening, so we trigger a first refresh state to
+ // set the initial state
+ mLockTile.refreshState();
+
+ mTestableLooper.processAllMessages();
+
+ mContext.setMockPackageManager(mPackageManager);
+ doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName();
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+ Manifest.permission.CAMERA, PACKAGE_NAME);
+
+ when(mBatteryController.isPowerSave()).thenReturn(false);
+ when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(false);
+ enableAutoRotation();
+ enableCameraBasedRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ public void testSecondaryString_cameraRotateOn_returnsFaceBased() {
+ assertEquals("On - Face-based", mLockTile.getState().secondaryLabel);
+ }
+
+ @Test
+ public void testSecondaryString_rotateOff_isEmpty() {
+ disableAutoRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_cameraRotateOff_isEmpty() {
+ disableCameraBasedRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_powerSaveEnabled_isEmpty() {
+ when(mBatteryController.isPowerSave()).thenReturn(true);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_cameraDisabled_isEmpty() {
+ when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(true);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_noCameraPermission_isEmpty() {
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ Manifest.permission.CAMERA, PACKAGE_NAME);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ private void enableAutoRotation() {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 1, UserHandle.USER_CURRENT);
+ }
+
+ private void disableAutoRotation() {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT);
+ }
+
+ private void enableCameraBasedRotation() {
+ mSecureSettings.putIntForUser(
+ CAMERA_AUTOROTATE, 1, UserHandle.USER_CURRENT);
+ }
+
+ private void disableCameraBasedRotation() {
+ mSecureSettings.putIntForUser(
+ CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
new file mode 100644
index 0000000..77946cf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
@@ -0,0 +1,167 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class InternetAdapterTest extends SysuiTestCase {
+
+ private static final String WIFI_TITLE = "Wi-Fi Title";
+ private static final String WIFI_SUMMARY = "Wi-Fi Summary";
+ private static final int GEAR_ICON_RES_ID = R.drawable.ic_settings_24dp;
+ private static final int LOCK_ICON_RES_ID = R.drawable.ic_friction_lock_closed;
+
+ @Rule
+ public MockitoRule mRule = MockitoJUnit.rule();
+
+ @Mock
+ private WifiEntry mInternetWifiEntry;
+ @Mock
+ private List<WifiEntry> mWifiEntries;
+ @Mock
+ private WifiEntry mWifiEntry;
+ @Mock
+ private InternetDialogController mInternetDialogController;
+ @Mock
+ private WifiUtils.InternetIconInjector mWifiIconInjector;
+ @Mock
+ private Drawable mGearIcon;
+ @Mock
+ private Drawable mLockIcon;
+
+ private TestableResources mTestableResources;
+ private InternetAdapter mInternetAdapter;
+ private InternetAdapter.InternetViewHolder mViewHolder;
+
+ @Before
+ public void setUp() {
+ mTestableResources = mContext.getOrCreateTestableResources();
+ when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+ when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
+ when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+
+ mInternetAdapter = new InternetAdapter(mInternetDialogController);
+ mViewHolder = mInternetAdapter.onCreateViewHolder(new LinearLayout(mContext), 0);
+ mInternetAdapter.setWifiEntries(Arrays.asList(mWifiEntry), 1 /* wifiEntriesCount */);
+ mViewHolder.mWifiIconInjector = mWifiIconInjector;
+ }
+
+ @Test
+ public void getItemCount_returnWifiEntriesCount() {
+ for (int i = 0; i < InternetDialogController.MAX_WIFI_ENTRY_COUNT; i++) {
+ mInternetAdapter.setWifiEntries(mWifiEntries, i /* wifiEntriesCount */);
+
+ assertThat(mInternetAdapter.getItemCount()).isEqualTo(i);
+ }
+ }
+
+ @Test
+ public void onBindViewHolder_bindWithOpenWifiNetwork_verifyView() {
+ when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_NONE);
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onBindViewHolder_bindWithSecurityWifiNetwork_verifyView() {
+ when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_PSK);
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void onBindViewHolder_wifiLevelUnreachable_shouldNotGetWifiIcon() {
+ reset(mWifiIconInjector);
+ when(mWifiEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector, never()).getIcon(anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void onBindViewHolder_shouldNotShowXLevelIcon_getIconWithInternet() {
+ when(mWifiEntry.shouldShowXLevelIcon()).thenReturn(false);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector).getIcon(eq(false) /* noInternet */, anyInt());
+ }
+
+ @Test
+ public void onBindViewHolder_shouldShowXLevelIcon_getIconWithNoInternet() {
+ when(mWifiEntry.shouldShowXLevelIcon()).thenReturn(true);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector).getIcon(eq(true) /* noInternet */, anyInt());
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiConnected_updateGearIcon() {
+ mTestableResources.addOverride(GEAR_ICON_RES_ID, mGearIcon);
+
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_CONNECTED, WifiEntry.SECURITY_PSK);
+
+ assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mGearIcon);
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityPsk_updateLockIcon() {
+ mTestableResources.addOverride(LOCK_ICON_RES_ID, mLockIcon);
+
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_PSK);
+
+ assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mLockIcon);
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityNone_hideIcon() {
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_NONE);
+
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
new file mode 100644
index 0000000..f9d5be6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -0,0 +1,646 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
+import com.android.systemui.toast.SystemUIToast;
+import com.android.systemui.toast.ToastFactory;
+import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.wifitrackerlib.MergedCarrierEntry;
+import com.android.wifitrackerlib.WifiEntry;
+
+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.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class InternetDialogControllerTest extends SysuiTestCase {
+
+ private static final int SUB_ID = 1;
+ private static final String CONNECTED_TITLE = "Connected Wi-Fi Title";
+ private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary";
+
+ //SystemUIToast
+ private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
+ private static final int TOAST_MESSAGE_STRING_ID = 1;
+ private static final String TOAST_MESSAGE_STRING = "toast message";
+
+ @Mock
+ private WifiManager mWifiManager;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private SubscriptionManager mSubscriptionManager;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private Handler mWorkerHandler;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private GlobalSettings mGlobalSettings;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private NetworkController.AccessPointController mAccessPointController;
+ @Mock
+ private WifiEntry mConnectedEntry;
+ @Mock
+ private WifiEntry mWifiEntry1;
+ @Mock
+ private WifiEntry mWifiEntry2;
+ @Mock
+ private WifiEntry mWifiEntry3;
+ @Mock
+ private WifiEntry mWifiEntry4;
+ @Mock
+ private MergedCarrierEntry mMergedCarrierEntry;
+ @Mock
+ private ServiceState mServiceState;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private WifiUtils.InternetIconInjector mWifiIconInjector;
+ @Mock
+ InternetDialogController.InternetDialogCallback mInternetDialogCallback;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private ToastFactory mToastFactory;
+ @Mock
+ private SystemUIToast mSystemUIToast;
+ @Mock
+ private View mToastView;
+ @Mock
+ private Animator mAnimator;
+ @Mock
+ private CarrierConfigTracker mCarrierConfigTracker;
+
+ private TestableResources mTestableResources;
+ private MockInternetDialogController mInternetDialogController;
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private List<WifiEntry> mAccessPoints = new ArrayList<>();
+ private List<WifiEntry> mWifiEntries = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTestableResources = mContext.getOrCreateTestableResources();
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+ when(mConnectedEntry.isDefaultNetwork()).thenReturn(true);
+ when(mConnectedEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry2.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry3.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry4.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
+ when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
+ .thenReturn(mSystemUIToast);
+ when(mSystemUIToast.getView()).thenReturn(mToastView);
+ when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
+ when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator);
+
+ mInternetDialogController = new MockInternetDialogController(mContext,
+ mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
+ mSubscriptionManager, mTelephonyManager, mWifiManager,
+ mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
+ mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
+ mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker);
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
+ mInternetDialogController.mOnSubscriptionsChangedListener);
+ mInternetDialogController.onStart(mInternetDialogCallback, true);
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+ mInternetDialogController.mActivityStarter = mActivityStarter;
+ mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+ }
+
+ @Test
+ public void connectCarrierNetwork_mergedCarrierEntryCanConnect_connectAndCreateSysUiToast() {
+ when(mMergedCarrierEntry.canConnect()).thenReturn(true);
+ mTestableResources.addOverride(R.string.wifi_wont_autoconnect_for_now,
+ TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.connectCarrierNetwork();
+
+ verify(mMergedCarrierEntry).connect(null /* callback */, false /* showToast */);
+ verify(mToastFactory).createToast(any(), eq(TOAST_MESSAGE_STRING), anyString(), anyInt(),
+ anyInt());
+ }
+
+ @Test
+ public void makeOverlayToast_withGravityFlags_addViewWithLayoutParams() {
+ mTestableResources.addOverride(TOAST_MESSAGE_STRING_ID, TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.makeOverlayToast(TOAST_MESSAGE_STRING_ID);
+
+ ArgumentCaptor<WindowManager.LayoutParams> paramsCaptor = ArgumentCaptor.forClass(
+ WindowManager.LayoutParams.class);
+ verify(mWindowManager).addView(eq(mToastView), paramsCaptor.capture());
+ WindowManager.LayoutParams params = paramsCaptor.getValue();
+ assertThat(params.format).isEqualTo(PixelFormat.TRANSLUCENT);
+ assertThat(params.type).isEqualTo(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ assertThat(params.horizontalWeight).isEqualTo(TOAST_PARAMS_HORIZONTAL_WEIGHT);
+ assertThat(params.verticalWeight).isEqualTo(TOAST_PARAMS_VERTICAL_WEIGHT);
+ }
+
+ @Test
+ public void makeOverlayToast_withAnimation_verifyAnimatorStart() {
+ mTestableResources.addOverride(TOAST_MESSAGE_STRING_ID, TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.makeOverlayToast(TOAST_MESSAGE_STRING_ID);
+
+ verify(mAnimator).start();
+ }
+
+ @Test
+ public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() {
+ mInternetDialogController.setAirplaneModeEnabled(true);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
+ getResourcesString("airplane_mode")));
+ }
+
+ @Test
+ public void getDialogTitleText_withAirplaneModeOff_returnInternet() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
+ getResourcesString("quick_settings_internet_label")));
+ }
+
+ @Test
+ public void getSubtitleText_withAirplaneModeOn_returnNull() {
+ mInternetDialogController.setAirplaneModeEnabled(true);
+
+ assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
+ }
+
+ @Test
+ public void getSubtitleText_withWifiOff_returnWifiIsOff() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("wifi_is_off"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("wifi_is_off"));
+ }
+
+ @Test
+ public void getSubtitleText_withNoWifiEntry_returnSearchWifi() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ assertThat(mInternetDialogController.getSubtitleText(true))
+ .isEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(true))
+ .isNotEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+ }
+
+ @Test
+ public void getSubtitleText_withWifiEntry_returnTapToConnect() {
+ // The preconditions WiFi Entries is already in setUp()
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("tap_a_network_to_connect"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("tap_a_network_to_connect"));
+ }
+
+ @Test
+ public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ getResourcesString("unlock_to_view_networks")));
+ }
+
+ @Test
+ public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+ doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+ doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState();
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+ }
+
+ @Test
+ public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
+ doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+
+ when(mTelephonyManager.isDataEnabled()).thenReturn(false);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("non_carrier_network_unavailable"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("non_carrier_network_unavailable"));
+ }
+
+ @Test
+ public void getWifiDetailsSettingsIntent_withNoKey_returnNull() {
+ assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull();
+ }
+
+ @Test
+ public void getWifiDetailsSettingsIntent_withKey_returnIntent() {
+ assertThat(mInternetDialogController.getWifiDetailsSettingsIntent("test_key")).isNotNull();
+ }
+
+ @Test
+ public void getInternetWifiDrawable_withConnectedEntry_returnIntentIconWithCorrectColor() {
+ final Drawable drawable = mock(Drawable.class);
+ when(mWifiIconInjector.getIcon(anyBoolean(), anyInt())).thenReturn(drawable);
+
+ mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+ verify(mWifiIconInjector).getIcon(eq(false), anyInt());
+ verify(drawable).setTint(mContext.getColor(R.color.connected_network_primary_color));
+ }
+
+ @Test
+ public void getInternetWifiDrawable_withWifiLevelUnreachable_returnNull() {
+ when(mConnectedEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
+
+ Drawable drawable = mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+ assertThat(drawable).isNull();
+ }
+
+ @Test
+ public void launchWifiNetworkDetailsSetting_withNoWifiEntryKey_doNothing() {
+ mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */);
+
+ verify(mActivityStarter, never())
+ .postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ }
+
+ @Test
+ public void launchWifiNetworkDetailsSetting_withWifiEntryKey_startActivity() {
+ mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key");
+
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ }
+
+ @Test
+ public void isDeviceLocked_keyguardIsUnlocked_returnFalse() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+
+ assertThat(mInternetDialogController.isDeviceLocked()).isFalse();
+ }
+
+ @Test
+ public void isDeviceLocked_keyguardIsLocked_returnTrue() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ assertThat(mInternetDialogController.isDeviceLocked()).isTrue();
+ }
+
+ @Test
+ public void onAccessPointsChanged_canNotConfigWifi_doNothing() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.mCanConfigWifi = false;
+
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any());
+ }
+
+ @Test
+ public void onAccessPointsChanged_nullAccessPoints_callbackBothNull() {
+ reset(mInternetDialogCallback);
+
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mWifiEntry1);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+ mAccessPoints.add(mWifiEntry4);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+ mAccessPoints.add(mWifiEntry4);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ mWifiEntries.add(mWifiEntry4);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+
+ // If the Ethernet exists, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.mHasEthernet = true;
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry4);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(true);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+
+ verify(mMergedCarrierEntry, never()).setEnabled(anyBoolean());
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_mergedCarrierEntryEmpty_doesntCrash() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(false);
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(null);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_neededSetMergedCarrierEntry_setTogether() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(false);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+
+ verify(mMergedCarrierEntry).setEnabled(true);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, false);
+
+ verify(mMergedCarrierEntry).setEnabled(false);
+ }
+
+ private String getResourcesString(String name) {
+ return mContext.getResources().getString(getResourcesId(name));
+ }
+
+ private int getResourcesId(String name) {
+ return mContext.getResources().getIdentifier(name, "string",
+ mContext.getPackageName());
+ }
+
+ private class MockInternetDialogController extends InternetDialogController {
+
+ private GlobalSettings mGlobalSettings;
+ private boolean mIsAirplaneModeOn;
+
+ MockInternetDialogController(Context context, UiEventLogger uiEventLogger,
+ ActivityStarter starter, AccessPointController accessPointController,
+ SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
+ @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+ @Main Handler handler, @Main Executor mainExecutor,
+ BroadcastDispatcher broadcastDispatcher,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
+ KeyguardStateController keyguardStateController, WindowManager windowManager,
+ ToastFactory toastFactory, Handler workerHandler,
+ CarrierConfigTracker carrierConfigTracker) {
+ super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
+ telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
+ broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
+ keyguardStateController, windowManager, toastFactory, workerHandler,
+ carrierConfigTracker);
+ mGlobalSettings = globalSettings;
+ }
+
+ @Override
+ boolean isAirplaneModeEnabled() {
+ return mIsAirplaneModeOn;
+ }
+
+ public void setAirplaneModeEnabled(boolean enabled) {
+ mIsAirplaneModeOn = enabled;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
new file mode 100644
index 0000000..c42b64a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -0,0 +1,334 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class InternetDialogTest extends SysuiTestCase {
+
+ private static final String MOBILE_NETWORK_TITLE = "Mobile Title";
+ private static final String MOBILE_NETWORK_SUMMARY = "Mobile Summary";
+ private static final String WIFI_TITLE = "Connected Wi-Fi Title";
+ private static final String WIFI_SUMMARY = "Connected Wi-Fi Summary";
+
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private WifiManager mWifiManager;
+ @Mock
+ private WifiEntry mInternetWifiEntry;
+ @Mock
+ private List<WifiEntry> mWifiEntries;
+ @Mock
+ private InternetAdapter mInternetAdapter;
+ @Mock
+ private InternetDialogController mInternetDialogController;
+
+ private InternetDialog mInternetDialog;
+ private View mDialogView;
+ private View mSubTitle;
+ private LinearLayout mEthernet;
+ private LinearLayout mMobileDataToggle;
+ private LinearLayout mWifiToggle;
+ private LinearLayout mConnectedWifi;
+ private RecyclerView mWifiList;
+ private LinearLayout mSeeAll;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+ when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
+ when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntries.size()).thenReturn(1);
+
+ when(mInternetDialogController.getMobileNetworkTitle()).thenReturn(MOBILE_NETWORK_TITLE);
+ when(mInternetDialogController.getMobileNetworkSummary())
+ .thenReturn(MOBILE_NETWORK_SUMMARY);
+ when(mInternetDialogController.getWifiManager()).thenReturn(mWifiManager);
+
+ mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class),
+ mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler);
+ mInternetDialog.mAdapter = mInternetAdapter;
+ mInternetDialog.onAccessPointsChanged(mWifiEntries, mInternetWifiEntry);
+ mInternetDialog.show();
+
+ mDialogView = mInternetDialog.mDialogView;
+ mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+ mEthernet = mDialogView.requireViewById(R.id.ethernet_layout);
+ mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+ mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout);
+ mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout);
+ mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
+ }
+
+ @After
+ public void tearDown() {
+ mInternetDialog.dismissDialog();
+ }
+
+ @Test
+ public void hideWifiViews_WifiViewsGone() {
+ mInternetDialog.hideWifiViews();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOn_internetDialogSubTitleGone() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mSubTitle.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOff_internetDialogSubTitleVisible() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOffAndHasEthernet_showEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+ when(mInternetDialogController.hasEthernet()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOffAndNoEthernet_hideEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+ when(mInternetDialogController.hasEthernet()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_apmOnAndHasEthernet_showEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ when(mInternetDialogController.hasEthernet()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOnAndNoEthernet_hideEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ when(mInternetDialogController.hasEthernet()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOn_mobileDataLayoutGone() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndNoConnectedWifi_hideConnectedWifi() {
+ // The precondition WiFi ON is already in setUp()
+ mInternetDialog.onAccessPointsChanged(mWifiEntries, null /* connectedEntry*/);
+ doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndNoWifiList_hideWifiListAndSeeAll() {
+ // The precondition WiFi ON is already in setUp()
+ mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, mInternetWifiEntry);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasInternetWifi_showHighlightWifiToggle() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWifiToggle.getBackground()).isNotNull();
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasInternetWifi_hideConnectedWifi() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasWifiList_hideWifiListAndSeeAll() {
+ // The preconditions WiFi entries are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
+ mSeeAll.performClick();
+
+ verify(mInternetDialogController).launchNetworkSetting();
+ }
+
+ @Test
+ public void showProgressBar_wifiDisabled_hideProgressBar() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ mInternetDialog.showProgressBar();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+ }
+
+ @Test
+ public void showProgressBar_deviceLocked_hideProgressBar() {
+ Mockito.reset(mHandler);
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.showProgressBar();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+ }
+
+ @Test
+ public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ mInternetDialog.showProgressBar();
+
+ // Show progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).postDelayed(runnableCaptor.capture(),
+ eq(InternetDialog.PROGRESS_DELAY_MS));
+ runnableCaptor.getValue().run();
+
+ // Then hide progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ }
+
+ @Test
+ public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry*/);
+
+ mInternetDialog.showProgressBar();
+
+ // Show progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).postDelayed(runnableCaptor.capture(),
+ eq(InternetDialog.PROGRESS_DELAY_MS));
+ runnableCaptor.getValue().run();
+
+ // Then hide searching sub-title only
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+ assertThat(mInternetDialog.mIsSearchingHidden).isTrue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
index 10c878a..6f081c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
@@ -34,6 +34,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -274,7 +275,8 @@
when(client.start(/* response */ any(), /* maxPages */ anyFloat()))
.thenReturn(immediateFuture(session));
return new ScrollCaptureController(context, context.getMainExecutor(),
- client, new ImageTileSet(context.getMainThreadHandler()));
+ client, new ImageTileSet(context.getMainThreadHandler()),
+ new UiEventLoggerFake());
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 21c6292..f3762c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -423,17 +423,18 @@
final boolean credentialAllowed = true;
final boolean requireConfirmation = true;
final int userId = 10;
- final String packageName = "test";
final long operationId = 1;
+ final String packageName = "test";
+ final long requestId = 10;
final int multiSensorConfig = BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
mCommandQueue.showAuthenticationDialog(promptInfo, receiver, sensorIds,
- credentialAllowed, requireConfirmation , userId, packageName, operationId,
+ credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId,
multiSensorConfig);
waitForIdleSync();
verify(mCallbacks).showAuthenticationDialog(eq(promptInfo), eq(receiver), eq(sensorIds),
- eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(packageName),
- eq(operationId), eq(multiSensorConfig));
+ eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(operationId),
+ eq(packageName), eq(requestId), eq(multiSensorConfig));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index f5ce673..f5cab1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -23,6 +23,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
@@ -111,6 +112,7 @@
private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo",
"bar");
+ private String mKeyguardTryFingerprintMsg;
private String mDisclosureWithOrganization;
private String mDisclosureGeneric;
private String mFinancedDisclosureWithOrganization;
@@ -182,6 +184,7 @@
mContext.addMockSystemService(UserManager.class, mUserManager);
mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
+ mKeyguardTryFingerprintMsg = mContext.getString(R.string.keyguard_try_fingerprint);
mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
ORGANIZATION_NAME);
mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
@@ -637,7 +640,7 @@
}
@Test
- public void onRefreshBatteryInfo_fullChargedWithOverheat_presentCharged() {
+ public void onRefreshBatteryInfo_fullChargedWithOverheat_presentChargingLimited() {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
@@ -649,6 +652,24 @@
verifyIndicationMessage(
INDICATION_TYPE_BATTERY,
+ mContext.getString(
+ R.string.keyguard_plugged_in_charging_limited,
+ NumberFormat.getPercentInstance().format(100 / 100f)));
+ }
+
+ @Test
+ public void onRefreshBatteryInfo_fullChargedWithoutOverheat_presentCharged() {
+ createController();
+ BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+ 100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
+ BatteryManager.BATTERY_HEALTH_GOOD, 0 /* maxChargingWattage */,
+ true /* present */);
+
+ mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+ mController.setVisible(true);
+
+ verifyIndicationMessage(
+ INDICATION_TYPE_BATTERY,
mContext.getString(R.string.keyguard_charged));
}
@@ -677,6 +698,48 @@
verifyTransientMessage(message);
}
+ @Test
+ public void faceAuthMessageSuppressed() {
+ createController();
+ String faceHelpMsg = "Face auth help message";
+
+ // GIVEN state of showing message when keyguard screen is on
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
+
+ // GIVEN fingerprint is also running (not udfps)
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsAvailable()).thenReturn(false);
+
+ mController.setVisible(true);
+
+ // WHEN a face help message comes in
+ mController.getKeyguardCallback().onBiometricHelp(
+ KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, faceHelpMsg,
+ BiometricSourceType.FACE);
+
+ // THEN "try fingerprint" message appears (and not the face help message)
+ verifyTransientMessage(mKeyguardTryFingerprintMsg);
+
+ // THEN the face help message is still announced for a11y
+ verify(mIndicationAreaBottom).announceForAccessibility(eq(faceHelpMsg));
+ }
+
+ @Test
+ public void testEmptyOwnerInfoHidesIndicationArea() {
+ createController();
+
+ // GIVEN the owner info is set to an empty string
+ when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn("");
+
+ // WHEN asked to update the indication area
+ mController.setVisible(true);
+
+ // THEN the owner info should be hidden
+ verifyHideIndication(INDICATION_TYPE_OWNER_INFO);
+ }
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
new file mode 100644
index 0000000..97fe25d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.Consumer
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LightRevealScrimTest : SysuiTestCase() {
+
+ private lateinit var scrim: LightRevealScrim
+ private var isOpaque = false
+
+ @Before
+ fun setUp() {
+ scrim = LightRevealScrim(context, null)
+ scrim.isScrimOpaqueChangedListener = Consumer { opaque ->
+ isOpaque = opaque
+ }
+ scrim.revealAmount = 0f
+ assertTrue("Scrim is not opaque in initial setup", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testAlphaSetsOpaque() {
+ scrim.alpha = 0.5f
+ assertFalse("Scrim is opaque even though alpha is set", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testVisibilitySetsOpaque() {
+ scrim.visibility = View.INVISIBLE
+ assertFalse("Scrim is opaque even though it's invisible", scrim.isScrimOpaque)
+ scrim.visibility = View.GONE
+ assertFalse("Scrim is opaque even though it's gone", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testRevealSetsOpaque() {
+ scrim.revealAmount = 0.5f
+ assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index a7b1446..465370b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -25,6 +25,7 @@
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
@@ -32,6 +33,7 @@
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -167,6 +169,22 @@
}
@Test
+ fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
+ notificationShadeDepthController.panelPullDownMinFraction = 0.5f
+ notificationShadeDepthController.onPanelExpansionChanged(0.5f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
+
+ notificationShadeDepthController.onPanelExpansionChanged(0.75f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0.5f)
+
+ notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(1f)
+ }
+
+ @Test
fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
onPanelExpansionChanged_apliesBlur_ifShade()
clearInvocations(choreographer)
@@ -178,10 +196,21 @@
@Test
fun setQsPanelExpansion_appliesBlur() {
+ statusBarState = StatusBarState.KEYGUARD
notificationShadeDepthController.qsPanelExpansion = 1f
- notificationShadeDepthController.onPanelExpansionChanged(0.5f, tracking = false)
+ notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(blurUtils).applyBlur(any(), anyInt(), eq(false))
+ verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
+ }
+
+ @Test
+ fun setQsPanelExpansion_easing() {
+ statusBarState = StatusBarState.KEYGUARD
+ notificationShadeDepthController.qsPanelExpansion = 0.25f
+ notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(wallpaperManager).setWallpaperZoomOut(any(),
+ eq(Interpolators.getNotificationScrimAlpha(0.25f, false /* notifications */)))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 116f807..9e103d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
import com.android.systemui.util.concurrency.FakeExecution
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -90,6 +92,8 @@
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
+ private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock
private lateinit var handler: Handler
@Mock
@@ -107,12 +111,15 @@
private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
@Captor
private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+ @Captor
+ private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener>
private lateinit var sessionListener: OnTargetsAvailableListener
private lateinit var userListener: UserTracker.Callback
private lateinit var settingsObserver: ContentObserver
private lateinit var configChangeListener: ConfigurationListener
private lateinit var statusBarStateListener: StateListener
+ private lateinit var deviceProvisionedListener: DeviceProvisionedListener
private val clock = FakeSystemClock()
private val executor = FakeExecutor(clock)
@@ -144,6 +151,8 @@
`when`(plugin.getView(any())).thenReturn(fakeSmartspaceView)
`when`(userTracker.userProfiles).thenReturn(userList)
`when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
setActiveUser(userHandlePrimary)
setAllowPrivateNotifications(userHandlePrimary, true)
@@ -161,11 +170,15 @@
contentResolver,
configurationController,
statusBarStateController,
+ deviceProvisionedController,
execution,
executor,
handler,
Optional.of(plugin)
)
+
+ verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
+ deviceProvisionedListener = deviceProvisionedCaptor.value
}
@Test(expected = RuntimeException::class)
@@ -180,6 +193,27 @@
}
@Test
+ fun connectOnlyAfterDeviceIsProvisioned() {
+ // GIVEN an unprovisioned device and an attempt to connect
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
+
+ // WHEN a connection attempt is made
+ controller.buildAndConnectView(fakeParent)
+
+ // THEN no session is created
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+ // WHEN it does become provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onUserSetupChanged()
+
+ // THEN the session is created
+ verify(smartspaceManager).createSmartspaceSession(any())
+ }
+
+ @Test
fun testListenersAreRegistered() {
// GIVEN a listener is added after a session is created
connectSession()
@@ -424,6 +458,20 @@
assertEquals(fakeSmartspaceView, controller.view)
}
+ @Test
+ fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
+ // GIVEN an uninitalized smartspaceView
+ // WHEN the device is provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onDeviceProvisionedChanged()
+
+ // THEN no calls to createSmartspaceSession should occur
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+ // THEN no listeners should be registered
+ verify(configurationController, never()).addCallback(any())
+ }
+
private fun connectSession() {
controller.buildAndConnectView(fakeParent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 690b841..1043faa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -16,24 +16,37 @@
package com.android.systemui.statusbar.phone;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.AdditionalAnswers.returnsFirstArg;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.util.BurnInHelperKt;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private static final int SCREEN_HEIGHT = 2000;
- private static final int EMPTY_MARGIN = 0;
private static final int EMPTY_HEIGHT = 0;
private static final float ZERO_DRAG = 0.f;
private static final float OPAQUE = 1.f;
@@ -41,10 +54,15 @@
private static final boolean HAS_CUSTOM_CLOCK = false;
private static final boolean HAS_VISIBLE_NOTIFS = false;
+ @Mock
+ private Resources mResources;
+
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
+ private MockitoSession mStaticMockSession;
private int mNotificationStackHeight;
private float mPanelExpansion;
+ private int mKeyguardStatusBarHeaderHeight;
private int mKeyguardStatusHeight;
private float mDark;
private boolean mHasCustomClock;
@@ -52,16 +70,32 @@
private float mQsExpansion;
private int mCutoutTopInset = 0; // in pixels
private boolean mIsSplitShade = false;
+ private float mUdfpsTop = -1;
+ private float mClockBottom = SCREEN_HEIGHT / 2;
+ private boolean mClockTopAligned;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession = mockitoSession()
+ .mockStatic(BurnInHelperKt.class)
+ .startMocking();
+
mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+ when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
+ mClockPositionAlgorithm.loadDimens(mResources);
+
mClockPosition = new KeyguardClockPositionAlgorithm.Result();
mHasCustomClock = HAS_CUSTOM_CLOCK;
mHasVisibleNotifs = HAS_VISIBLE_NOTIFS;
}
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
@Test
public void clockPositionTopOfScreenOnAOD() {
// GIVEN on AOD and both stack scroll and clock have 0 height
@@ -338,6 +372,155 @@
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
}
+ @Test
+ public void clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN the clock + udfps are 100px apart
+ mClockBottom = SCREEN_HEIGHT - 500;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the worst-case clock Y position is shifted only by 100 (not the full 200),
+ // so that it's at the same location as mUdfpsTop
+ assertThat(mClockPosition.clockY).isEqualTo(100);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ @Test
+ public void clockPositionShiftsToAvoidUdfpsOnAOD_usesSpaceAboveClock() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD)
+ mKeyguardStatusBarHeaderHeight = 150;
+
+ // GIVEN the bottom of the clock is beyond the top of UDFPS
+ mClockBottom = SCREEN_HEIGHT - 300;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use the area above the clock for
+ // burn-in since the burn in offset > space above clock
+ assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset (0 in this case)
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ @Test
+ public void clockPositionShiftsToAvoidUdfpsOnAOD_usesMaxBurnInOffset() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD) but 50px are taken up by the cutout
+ mKeyguardStatusBarHeaderHeight = 200;
+ mCutoutTopInset = 50;
+
+ // GIVEN the bottom of the clock is beyond the top of UDFPS
+ mClockBottom = SCREEN_HEIGHT - 300;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is only 25px (less than space above clock)
+ givenAOD();
+ int maxYBurnInOffset = 25;
+ givenMaxBurnInOffset(maxYBurnInOffset);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use the area above the clock for
+ // burn-in
+ assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts above mKeyguardStatusBarHeaderHeight
+ assertThat(mClockPosition.clockY).isEqualTo(
+ mKeyguardStatusBarHeaderHeight - 2 * maxYBurnInOffset);
+ }
+
+ @Test
+ public void clockPositionShiftsToMaximizeUdfpsBurnInMovement() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD) but 50px are taken up by the cutout
+ mKeyguardStatusBarHeaderHeight = 200;
+ mCutoutTopInset = 50;
+ int upperSpaceAvailable = mKeyguardStatusBarHeaderHeight - mCutoutTopInset;
+
+ // GIVEN the bottom of the clock and the top of UDFPS are 100px apart
+ mClockBottom = SCREEN_HEIGHT - 500;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+ float lowerSpaceAvailable = mUdfpsTop - mClockBottom;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use both the area above
+ // the clock and below the clock (vertically centered in its allowed area)
+ assertThat(mClockPosition.clockY).isEqualTo(
+ (int) (mCutoutTopInset + upperSpaceAvailable + lowerSpaceAvailable));
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ private void givenHighestBurnInOffset() {
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg());
+ }
+
+ private void givenLowestBurnInOffset() {
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(0);
+ }
+
+ private void givenMaxBurnInOffset(int offset) {
+ when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_large_clock))
+ .thenReturn(offset);
+ mClockPositionAlgorithm.loadDimens(mResources);
+ }
+
private void givenAOD() {
mPanelExpansion = 1.f;
mDark = 1.f;
@@ -348,13 +531,33 @@
mDark = 0.f;
}
+ /**
+ * Setup and run the clock position algorithm.
+ *
+ * mClockPosition.clockY will contain the top y-coordinate for the clock position
+ */
private void positionClock() {
- mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
- mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
- 0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */,
- mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
- 0 /* unlockedStackScrollerPadding */, mQsExpansion,
- mCutoutTopInset, mIsSplitShade);
+ mClockPositionAlgorithm.setup(
+ mKeyguardStatusBarHeaderHeight,
+ SCREEN_HEIGHT,
+ mNotificationStackHeight,
+ mPanelExpansion,
+ SCREEN_HEIGHT,
+ mKeyguardStatusHeight,
+ 0 /* userSwitchHeight */,
+ 0 /* userSwitchPreferredY */,
+ mHasCustomClock,
+ mHasVisibleNotifs,
+ mDark,
+ ZERO_DRAG,
+ false /* bypassEnabled */,
+ 0 /* unlockedStackScrollerPadding */,
+ mQsExpansion,
+ mCutoutTopInset,
+ mIsSplitShade,
+ mUdfpsTop,
+ mClockBottom,
+ mClockTopAligned);
mClockPositionAlgorithm.run(mClockPosition);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index d7661be..f247788 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -141,7 +141,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class NotificationPanelViewTest extends SysuiTestCase {
+public class NotificationPanelViewControllerTest extends SysuiTestCase {
private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50;
@@ -470,6 +470,12 @@
}
@Test
+ public void testSetMinFraction() {
+ mNotificationPanelViewController.setMinFraction(0.5f);
+ verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
+ }
+
+ @Test
public void testSetDozing_notifiesNsslAndStateController() {
mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */,
null /* touch */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index ddd7854..90b8a74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -146,7 +146,7 @@
mNotificationShadeWindowController.attach();
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.setLightRevealScrimAmount(0f);
+ mNotificationShadeWindowController.setLightRevealScrimOpaque(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) == 0).isTrue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index 9b7c78f..aafaebd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -16,6 +16,11 @@
package com.android.systemui.statusbar.phone;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -56,6 +61,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -93,6 +100,10 @@
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock private LockIconViewController mLockIconViewController;
+ @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
+ mInteractionEventHandlerCaptor;
+ private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -150,4 +161,49 @@
verify(mDragDownHelper).onTouchEvent(ev);
ev.recycle();
}
+
+ @Test
+ public void testInterceptTouchWhenShowingAltAuth() {
+ captureInteractionEventHandler();
+
+ // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we should intercept touch
+ assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
+ }
+
+ @Test
+ public void testNoInterceptTouch() {
+ captureInteractionEventHandler();
+
+ // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we shouldn't intercept touch
+ assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
+ }
+
+ @Test
+ public void testHandleTouchEventWhenShowingAltAuth() {
+ captureInteractionEventHandler();
+
+ // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we should handle the touch
+ assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class)));
+ }
+
+ private void captureInteractionEventHandler() {
+ verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture());
+ mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue();
+
+ }
}
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
index 6de5866..47c8806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -624,7 +624,7 @@
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, false,
+ mScrimBehind, true,
mScrimForBubble, true
));
@@ -705,7 +705,7 @@
public void qsExpansion_half_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
- mScrimController.setQsPosition(0.5f, 999 /* value doesn't matter */);
+ mScrimController.setQsPosition(0.25f, 999 /* value doesn't matter */);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -1136,7 +1136,7 @@
@Test
public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
- mScrimController.setQsPosition(0.5f, 300);
+ mScrimController.setQsPosition(0.25f, 300);
assertScrimAlpha(Map.of(
mScrimBehind, SEMI_TRANSPARENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index c39a906..2b569f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -299,18 +299,4 @@
verify(mBouncer).updateKeyguardPosition(1.0f);
}
-
- @Test
- public void testNavBarHiddenWhenSleepAnimationStarts() {
- mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- assertTrue(mStatusBarKeyguardViewManager.isNavBarVisible());
-
- // Verify that the nav bar is hidden when the screen off animation starts
- doReturn(true).when(mUnlockedScreenOffAnimationController).isScreenOffAnimationPlaying();
- mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
- assertFalse(mStatusBarKeyguardViewManager.isNavBarVisible());
-
- mWakefulnessLifecycle.dispatchFinishedWakingUp();
- assertTrue(mStatusBarKeyguardViewManager.isNavBarVisible());
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index bd9835c..6051c56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -824,6 +824,34 @@
}
@Test
+ public void testSetExpansionAffectsAlpha_onlyWhenHidingKeyguard() {
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(true));
+
+ clearInvocations(mScrimController);
+ when(mBiometricUnlockController.isBiometricUnlock()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(true));
+
+ clearInvocations(mScrimController);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+
+ clearInvocations(mScrimController);
+ reset(mKeyguardStateController);
+ when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+
+ clearInvocations(mScrimController);
+ reset(mKeyguardStateController);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+ }
+
+ @Test
public void testTransitionLaunch_noPreview_doesntGoUnlocked() {
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
mStatusBar.showKeyguardImpl();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index d26db4c..b7c4d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -221,6 +221,18 @@
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
+ /** Regression test for b/201097913. */
+ @Test
+ fun onEntryCleanUp_callNotifAddedThenRemoved_listenerNotified() {
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockOngoingCallListener)
+
+ notifCollectionListener.onEntryCleanUp(ongoingCallNotifEntry)
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
+ }
+
/** Regression test for b/188491504. */
@Test
fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 57198db..4a5770d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -238,7 +238,7 @@
mNetworkController.setNoNetworksAvailable(false);
setWifiStateForVcn(true, testSsid);
setWifiLevelForVcn(0);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]);
+ verifyLastMobileDataIndicatorsForVcn(true, 0, TelephonyIcons.ICON_CWF, false);
mNetworkController.setNoNetworksAvailable(true);
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
@@ -246,11 +246,11 @@
setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
+ verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
+ verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
new file mode 100644
index 0000000..871a48c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 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.policy
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Date
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class VariableDateViewControllerTest : SysuiTestCase() {
+
+ companion object {
+ private const val TIME_STAMP = 1_500_000_000_000
+ private const val LONG_PATTERN = "EEEMMMd"
+ private const val SHORT_PATTERN = "MMMd"
+ private const val CHAR_WIDTH = 10f
+ }
+
+ @Mock
+ private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock
+ private lateinit var view: VariableDateView
+ @Captor
+ private lateinit var onMeasureListenerCaptor: ArgumentCaptor<VariableDateView.OnMeasureListener>
+
+ private var lastText: String? = null
+
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var testableHandler: Handler
+ private lateinit var controller: VariableDateViewController
+
+ private lateinit var longText: String
+ private lateinit var shortText: String
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ testableHandler = Handler(testableLooper.looper)
+
+ systemClock = FakeSystemClock()
+ systemClock.setCurrentTimeMillis(TIME_STAMP)
+
+ `when`(view.longerPattern).thenReturn(LONG_PATTERN)
+ `when`(view.shorterPattern).thenReturn(SHORT_PATTERN)
+ `when`(view.handler).thenReturn(testableHandler)
+
+ `when`(view.setText(anyString())).thenAnswer {
+ lastText = it.arguments[0] as? String
+ Unit
+ }
+ `when`(view.isAttachedToWindow).thenReturn(true)
+
+ val date = Date(TIME_STAMP)
+ longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN))
+ shortText = getTextForFormat(date, getFormatFromPattern(SHORT_PATTERN))
+
+ // Assume some sizes for the text, the controller doesn't need to know if these sizes are
+ // the true ones
+ `when`(view.getDesiredWidthForText(any())).thenAnswer {
+ getTextLength(it.arguments[0] as CharSequence)
+ }
+
+ controller = VariableDateViewController(
+ systemClock,
+ broadcastDispatcher,
+ testableHandler,
+ view
+ )
+
+ controller.init()
+ testableLooper.processAllMessages()
+
+ verify(view).onAttach(capture(onMeasureListenerCaptor))
+ }
+
+ @Test
+ fun testViewStartsWithLongText() {
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testListenerNotNull() {
+ assertThat(onMeasureListenerCaptor.value).isNotNull()
+ }
+
+ @Test
+ fun testLotsOfSpaceUseLongText() {
+ onMeasureListenerCaptor.value.onMeasureAction(10000)
+
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testSmallSpaceUseEmpty() {
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEmpty()
+ }
+
+ @Test
+ fun testSpaceInBetweenUseShortText() {
+ val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
+
+ onMeasureListenerCaptor.value.onMeasureAction(average)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEqualTo(shortText)
+ }
+
+ @Test
+ fun testSwitchBackToLonger() {
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+
+ onMeasureListenerCaptor.value.onMeasureAction(10000)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testNoSwitchingWhenFrozen() {
+ `when`(view.freezeSwitching).thenReturn(true)
+
+ val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
+ onMeasureListenerCaptor.value.onMeasureAction(average)
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ private fun getTextLength(text: CharSequence): Float {
+ return text.length * CHAR_WIDTH
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 9c47f19..2c461ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -103,7 +103,8 @@
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
+ mManager = new ThemeOverlayApplier(mOverlayManager,
+ MoreExecutors.directExecutor(), MoreExecutors.directExecutor(),
LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
@Override
protected OverlayManagerTransaction.Builder getTransactionBuilder() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
new file mode 100644
index 0000000..eebcbe6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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.usb
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.hardware.usb.IUsbSerialReader
+import android.hardware.usb.UsbAccessory
+import android.hardware.usb.UsbManager
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.Exception
+
+/**
+ * UsbPermissionActivityTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UsbPermissionActivityTest : SysuiTestCase() {
+
+ class UsbPermissionActivityTestable : UsbPermissionActivity()
+
+ @Rule
+ @JvmField
+ var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
+ UsbPermissionActivityTestable::class.java, false, false)
+
+ private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
+ .apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra(UsbManager.EXTRA_PACKAGE, "com.android.systemui")
+ putExtra(Intent.EXTRA_INTENT, PendingIntent.getBroadcast(
+ mContext,
+ 334,
+ Intent("NO_ACTION"),
+ PendingIntent.FLAG_MUTABLE))
+ putExtra(UsbManager.EXTRA_ACCESSORY, UsbAccessory(
+ "manufacturer",
+ "model",
+ "description",
+ "version",
+ "uri",
+ object : IUsbSerialReader.Stub() {
+ override fun getSerial(packageName: String): String {
+ return "serial"
+ }
+ }))
+ }
+
+ @Before
+ fun setUp() {
+ activityRule.launchActivity(activityIntent)
+ }
+
+ @After
+ fun tearDown() {
+ activityRule.finishActivity()
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testHideNonSystemOverlay() {
+ assertThat(activityRule.activity.window.attributes.privateFlags and
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ .isEqualTo(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
index a34c598..0e9d96c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.util.sensors;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -338,30 +340,25 @@
@Test
public void testSecondaryCancelsSecondary() {
TestableListener listener = new TestableListener();
- ThresholdSensor.Listener cancelingListener = new ThresholdSensor.Listener() {
- @Override
- public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) {
- mProximitySensor.pause();
- }
- };
+ ThresholdSensor.Listener cancelingListener = event -> mProximitySensor.pause();
mProximitySensor.register(listener);
mProximitySensor.register(cancelingListener);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorPrimary.triggerEvent(true, 0);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(true, 0);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
// The proximity sensor should now be canceled. Advancing the clock should do nothing.
- assertEquals(0, mFakeExecutor.numPending());
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(false, 1);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
mProximitySensor.unregister(listener);
}
@@ -372,33 +369,66 @@
TestableListener listener = new TestableListener();
- // WE immediately register the secondary sensor.
+ // We immediately register the secondary sensor.
mProximitySensor.register(listener);
- assertFalse(mThresholdSensorPrimary.isPaused());
- assertFalse(mThresholdSensorSecondary.isPaused());
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorPrimary.triggerEvent(true, 0);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(true, 0);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
// The secondary sensor should now remain resumed indefinitely.
- assertFalse(mThresholdSensorSecondary.isPaused());
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
mThresholdSensorSecondary.triggerEvent(false, 1);
- assertFalse(listener.mLastEvent.getBelow());
- assertEquals(2, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isFalse();
+ assertThat(listener.mCallCount).isEqualTo(2);
// The secondary is still running, and not polling with the executor.
- assertFalse(mThresholdSensorSecondary.isPaused());
- assertEquals(0, mFakeExecutor.numPending());
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
mProximitySensor.unregister(listener);
}
+ @Test
+ public void testSecondaryPausesPrimary() {
+ TestableListener listener = new TestableListener();
+
+ mProximitySensor.register(listener);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isFalse();
+ assertThat(mThresholdSensorSecondary.isPaused()).isTrue();
+
+ mProximitySensor.setSecondarySafe(true);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ }
+
+ @Test
+ public void testSecondaryResumesPrimary() {
+ mProximitySensor.setSecondarySafe(true);
+
+ TestableListener listener = new TestableListener();
+ mProximitySensor.register(listener);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+
+ mProximitySensor.setSecondarySafe(false);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isFalse();
+ assertThat(mThresholdSensorSecondary.isPaused()).isTrue();
+
+
+ }
+
private static class TestableListener implements ThresholdSensor.Listener {
ThresholdSensor.ThresholdSensorEvent mLastEvent;
int mCallCount = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
index 6976422..7bb2674 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
@@ -31,6 +31,7 @@
private final Map<SettingsKey, String> mValues = new HashMap<>();
private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
new HashMap<>();
+ private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
@@ -55,9 +56,15 @@
@Override
public void registerContentObserverForUser(Uri uri, boolean notifyDescendents,
ContentObserver settingsObserver, int userHandle) {
- SettingsKey key = new SettingsKey(userHandle, uri.toString());
- mContentObservers.putIfAbsent(key, new ArrayList<>());
- List<ContentObserver> observers = mContentObservers.get(key);
+ List<ContentObserver> observers;
+ if (userHandle == UserHandle.USER_ALL) {
+ mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
+ observers = mContentObserversAllUsers.get(uri.toString());
+ } else {
+ SettingsKey key = new SettingsKey(userHandle, uri.toString());
+ mContentObservers.putIfAbsent(key, new ArrayList<>());
+ observers = mContentObservers.get(key);
+ }
observers.add(settingsObserver);
}
@@ -67,6 +74,10 @@
List<ContentObserver> observers = mContentObservers.get(key);
observers.remove(settingsObserver);
}
+ for (String key : mContentObserversAllUsers.keySet()) {
+ List<ContentObserver> observers = mContentObserversAllUsers.get(key);
+ observers.remove(settingsObserver);
+ }
}
@Override
@@ -114,6 +125,10 @@
for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
observer.dispatchChange(false, List.of(uri), userHandle);
}
+ for (ContentObserver observer :
+ mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
+ observer.dispatchChange(false, List.of(uri), userHandle);
+ }
return true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 0d560f2..34cae58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.verify;
import android.database.ContentObserver;
+import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
@@ -89,6 +90,16 @@
}
@Test
+ public void testRegisterContentObserverAllUsers() {
+ mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
+
+ mFakeSettings.putString("cat", "hat");
+
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+ }
+
+ @Test
public void testUnregisterContentObserver() {
mFakeSettings.registerContentObserver("cat", mContentObserver);
mFakeSettings.unregisterContentObserver(mContentObserver);
@@ -98,4 +109,16 @@
verify(mContentObserver, never()).dispatchChange(
anyBoolean(), any(Collection.class), anyInt());
}
+
+ @Test
+ public void testUnregisterContentObserverAllUsers() {
+ mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
+ mFakeSettings.unregisterContentObserver(mContentObserver);
+
+ mFakeSettings.putString("cat", "hat");
+
+ verify(mContentObserver, never()).dispatchChange(
+ anyBoolean(), any(Collection.class), anyInt());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index be11024..4f9cb35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -51,6 +51,11 @@
}
@Override
+ public boolean isCameraRotationEnabled() {
+ return false;
+ }
+
+ @Override
public void setRotationLockedAtAngle(boolean locked, int rotation) {
}
diff --git a/packages/services/CameraExtensionsProxy/AndroidManifest.xml b/packages/services/CameraExtensionsProxy/AndroidManifest.xml
index d356894..ef1d581 100644
--- a/packages/services/CameraExtensionsProxy/AndroidManifest.xml
+++ b/packages/services/CameraExtensionsProxy/AndroidManifest.xml
@@ -8,6 +8,7 @@
android:directBootAware="true">
<service android:name=".CameraExtensionsProxyService"
+ android:visibleToInstantApps="true"
android:exported="true">
</service>
<uses-library android:name="androidx.camera.extensions.impl" android:required="false" />
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index fdba098..48f5b51 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -476,7 +476,11 @@
*/
public static void addTombstoneToDropBox(Context ctx, File tombstone, boolean proto) {
final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
- final String bootReason = SystemProperties.get("ro.boot.bootreason", null);
+ if (db == null) {
+ Slog.e(TAG, "Can't log tombstone: DropBoxManager not available");
+ return;
+ }
+
HashMap<String, Long> timestamps = readTimestamps();
try {
if (proto) {
@@ -484,7 +488,7 @@
} else {
final String headers = getBootHeadersToLogAndUpdate();
addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
- TAG_TOMBSTONE);
+ TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 483250a..68fd0c1 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -92,6 +92,8 @@
27533 notification_autogrouped (key|3)
# notification was removed from an autogroup
275534 notification_unautogrouped (key|3)
+# when a notification is adjusted via assistant
+27535 notification_adjusted (key|3),(adjustment_type|3),(new_value|3)
# ---------------------------
# Watchdog.java
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 75d0796..06a78c8 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -39,6 +39,7 @@
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
+import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
@@ -195,7 +196,7 @@
private EmergencyCallHelper mEmergencyCallHelper;
private KeyguardManager mKeyguardManager;
- private int mCurrentUser = -1;
+ private int mCurrentUser = USER_NULL;
public SensorPrivacyService(Context context) {
super(context);
@@ -228,9 +229,9 @@
@Override
public void onUserStarting(TargetUser user) {
- if (mCurrentUser == -1) {
+ if (mCurrentUser == USER_NULL) {
mCurrentUser = user.getUserIdentifier();
- mSensorPrivacyServiceImpl.userSwitching(-1, user.getUserIdentifier());
+ mSensorPrivacyServiceImpl.userSwitching(USER_NULL, user.getUserIdentifier());
}
}
@@ -420,9 +421,12 @@
}
synchronized (mLock) {
- if (mSuppressReminders.containsKey(new Pair<>(sensor, user))) {
+ UserHandle parentUser = UserHandle.of(mUserManagerInternal
+ .getProfileParentId(user.getIdentifier()));
+ if (mSuppressReminders.containsKey(new Pair<>(sensor, parentUser))) {
Log.d(TAG,
- "Suppressed sensor privacy reminder for " + packageName + "/" + user);
+ "Suppressed sensor privacy reminder for " + packageName + "/"
+ + parentUser);
return;
}
}
@@ -1291,13 +1295,13 @@
micState = isIndividualSensorPrivacyEnabledLocked(to, MICROPHONE);
camState = isIndividualSensorPrivacyEnabledLocked(to, CAMERA);
}
- if (prevMicState != micState) {
+ if (from == USER_NULL || prevMicState != micState) {
mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState);
setGlobalRestriction(MICROPHONE, micState);
}
- if (prevCamState != camState) {
+ if (from == USER_NULL || prevCamState != camState) {
mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState);
- setGlobalRestriction(CAMERA, micState);
+ setGlobalRestriction(CAMERA, camState);
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 69c2926..8727932 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -221,6 +221,9 @@
@GuardedBy("mLock")
private final Set<Integer> mFuseMountedUser = new ArraySet<>();
+ @GuardedBy("mLock")
+ private final Set<Integer> mCeStoragePreparedUsers = new ArraySet<>();
+
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
@@ -4864,5 +4867,19 @@
}
return primaryVolumeIds;
}
+
+ @Override
+ public void markCeStoragePrepared(int userId) {
+ synchronized (mLock) {
+ mCeStoragePreparedUsers.add(userId);
+ }
+ }
+
+ @Override
+ public boolean isCeStoragePrepared(int userId) {
+ synchronized (mLock) {
+ return mCeStoragePreparedUsers.contains(userId);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3f816c88..c911257 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7743,6 +7743,17 @@
} else {
killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
"Too many Binders sent to SYSTEM");
+ // We need to run a GC here, because killing the processes involved
+ // actually isn't guaranteed to free up the proxies; in fact, if the
+ // GC doesn't run for a long time, we may even exceed the global
+ // proxy limit for a process (20000), resulting in system_server itself
+ // being killed.
+ // Note that the GC here might not actually clean up all the proxies,
+ // because the binder reference decrements will come in asynchronously;
+ // but if new processes belonging to the UID keep adding proxies, we
+ // will get another callback here, and run the GC again - this time
+ // cleaning up the old proxies.
+ VMRuntime.getRuntime().requestConcurrentGC();
}
}, mHandler);
t.traceEnd(); // setBinderProxies
@@ -15413,12 +15424,14 @@
}
@Override
- public void updateDeviceIdleTempAllowlist(int[] appids, int changingUid, boolean adding,
- long durationMs, @TempAllowListType int type, @ReasonCode int reasonCode,
- @Nullable String reason, int callingUid) {
+ public void updateDeviceIdleTempAllowlist(@Nullable int[] appids, int changingUid,
+ boolean adding, long durationMs, @TempAllowListType int type,
+ @ReasonCode int reasonCode, @Nullable String reason, int callingUid) {
synchronized (ActivityManagerService.this) {
synchronized (mProcLock) {
- mDeviceIdleTempAllowlist = appids;
+ if (appids != null) {
+ mDeviceIdleTempAllowlist = appids;
+ }
if (adding) {
if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
// Note, the device idle temp-allowlist are by app-ids, but here
@@ -15428,12 +15441,7 @@
callingUid));
}
} else {
- // Note in the removing case, we need to remove all the UIDs matching
- // the appId, because DeviceIdle's temp-allowlist are based on AppIds,
- // not UIDs.
- // For eacmple, "cmd deviceidle tempallowlist -r PACKAGE" will
- // not only remove this app for user 0, but for all users.
- mFgsStartTempAllowList.removeAppId(UserHandle.getAppId(changingUid));
+ mFgsStartTempAllowList.removeUid(changingUid);
}
setAppIdTempAllowlistStateLSP(changingUid, adding);
}
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 0c633ca..7ba032f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -478,7 +478,7 @@
for (int uid : uidsToRemove) {
FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, uid,
FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED);
- mStats.removeIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
+ mStats.maybeRemoveIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis());
}
mStats.clearPendingRemovedUids();
diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java
index 50278fd..e6ffcfc 100644
--- a/services/core/java/com/android/server/am/CacheOomRanker.java
+++ b/services/core/java/com/android/server/am/CacheOomRanker.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import android.os.Process;
+import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.util.Slog;
@@ -38,21 +40,40 @@
private static final boolean DEFAULT_USE_OOM_RE_RANKING = false;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK = "oom_re_ranking_number_to_re_rank";
- @VisibleForTesting static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8;
+ @VisibleForTesting
+ static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS =
+ "oom_re_ranking_preserve_top_n_apps";
+ @VisibleForTesting
+ static final int DEFAULT_PRESERVE_TOP_N_APPS = 3;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_USE_FREQUENT_RSS = "oom_re_ranking_rss_use_frequent_rss";
+ @VisibleForTesting
+ static final boolean DEFAULT_USE_FREQUENT_RSS = true;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS = "oom_re_ranking_rss_update_rate_ms";
+ @VisibleForTesting
+ static final long DEFAULT_RSS_UPDATE_RATE_MS = 10_000; // 10 seconds
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_USES_WEIGHT = "oom_re_ranking_uses_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_RSS_WEIGHT = "oom_re_ranking_rss_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f;
private static final Comparator<RankedProcessRecord> SCORED_PROCESS_RECORD_COMPARATOR =
new ScoreComparator();
private static final Comparator<RankedProcessRecord> CACHE_USE_COMPARATOR =
new CacheUseComparator();
+ private static final Comparator<RankedProcessRecord> RSS_COMPARATOR =
+ new RssComparator();
private static final Comparator<RankedProcessRecord> LAST_RSS_COMPARATOR =
new LastRssComparator();
private static final Comparator<RankedProcessRecord> LAST_ACTIVITY_TIME_COMPARATOR =
@@ -61,20 +82,33 @@
private final Object mPhenotypeFlagLock = new Object();
private final ActivityManagerService mService;
+ private final ProcessDependencies mProcessDependencies;
private final ActivityManagerGlobalLock mProcLock;
private final Object mProfilerLock;
@GuardedBy("mPhenotypeFlagLock")
private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ int mPreserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ boolean mUseFrequentRss = DEFAULT_USE_FREQUENT_RSS;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ long mRssUpdateRateMs = DEFAULT_RSS_UPDATE_RATE_MS;
// Weight to apply to the LRU ordering.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT;
+ @VisibleForTesting
+ float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT;
// Weight to apply to the ordering by number of times the process has been added to the cache.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT;
+ @VisibleForTesting
+ float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT;
// Weight to apply to the ordering by RSS used by the processes.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT;
+ @VisibleForTesting
+ float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT;
// Positions to replace in the lru list.
@GuardedBy("mPhenotypeFlagLock")
@@ -93,6 +127,12 @@
updateUseOomReranking();
} else if (KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK.equals(name)) {
updateNumberToReRank();
+ } else if (KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS.equals(name)) {
+ updatePreserveTopNApps();
+ } else if (KEY_OOM_RE_RANKING_USE_FREQUENT_RSS.equals(name)) {
+ updateUseFrequentRss();
+ } else if (KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS.equals(name)) {
+ updateRssUpdateRateMs();
} else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) {
updateLruWeight();
} else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) {
@@ -106,9 +146,15 @@
};
CacheOomRanker(final ActivityManagerService service) {
+ this(service, new ProcessDependenciesImpl());
+ }
+
+ @VisibleForTesting
+ CacheOomRanker(final ActivityManagerService service, ProcessDependencies processDependencies) {
mService = service;
mProcLock = service.mProcLock;
mProfilerLock = service.mAppProfiler.mProfilerLock;
+ mProcessDependencies = processDependencies;
}
/** Load settings from device config and register a listener for changes. */
@@ -160,6 +206,31 @@
}
@GuardedBy("mPhenotypeFlagLock")
+ private void updatePreserveTopNApps() {
+ int preserveTopNApps = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, DEFAULT_PRESERVE_TOP_N_APPS);
+ if (preserveTopNApps < 0) {
+ Slog.w(OomAdjuster.TAG,
+ "Found negative value for preserveTopNApps, setting to default: "
+ + preserveTopNApps);
+ preserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS;
+ }
+ mPreserveTopNApps = preserveTopNApps;
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateRssUpdateRateMs() {
+ mRssUpdateRateMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS, DEFAULT_RSS_UPDATE_RATE_MS);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateUseFrequentRss() {
+ mUseFrequentRss = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_USE_FREQUENT_RSS, DEFAULT_USE_FREQUENT_RSS);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
private void updateLruWeight() {
mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_OOM_RE_RANKING_LRU_WEIGHT, DEFAULT_OOM_RE_RANKING_LRU_WEIGHT);
@@ -183,9 +254,39 @@
*/
@GuardedBy({"mService", "mProcLock"})
void reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart) {
+ // The lruList is a list of processes ordered by how recently they were used. The
+ // least-recently-used apps are at the beginning of the list. We keep track of two
+ // indices in the lruList:
+ //
+ // getNumberToReRank=5, preserveTopNApps=3, lruProcessServiceStart=7,
+ // lruList=
+ // 0: app A ^
+ // 1: app B | These apps are re-ranked, as they are the first five apps (see
+ // 2: app C | getNumberToReRank), excluding...
+ // 3: app D v
+ // 4: app E ^
+ // 5: app F | The three most-recently-used apps in the cache (see preserveTopNApps).
+ // 6: app G v
+ // 7: service A ^
+ // 8: service B | Everything beyond lruProcessServiceStart is ignored, as these aren't
+ // 9: service C | apps.
+ // 10: activity A |
+ // ... |
+ //
+ // `numProcessesEvaluated` moves across the apps (indices 0-6) or until we've found enough
+ // apps to re-rank, and made sure none of them are in the top `preserveTopNApps` apps.
+ // Re-ranked apps are copied into `scoredProcessRecords`, where the re-ranking calculation
+ // happens.
+ //
+ // Note that some apps in the `lruList` can be skipped, if they don't pass
+ //`appCanBeReRanked`.
+
float lruWeight;
float usesWeight;
float rssWeight;
+ int preserveTopNApps;
+ boolean useFrequentRss;
+ long rssUpdateRateMs;
int[] lruPositions;
RankedProcessRecord[] scoredProcessRecords;
@@ -193,6 +294,9 @@
lruWeight = mLruWeight;
usesWeight = mUsesWeight;
rssWeight = mRssWeight;
+ preserveTopNApps = mPreserveTopNApps;
+ useFrequentRss = mUseFrequentRss;
+ rssUpdateRateMs = mRssUpdateRateMs;
lruPositions = mLruPositions;
scoredProcessRecords = mScoredProcessRecords;
}
@@ -202,52 +306,98 @@
return;
}
+ int numProcessesEvaluated = 0;
// Collect the least recently used processes to re-rank, only rank cached
// processes further down the list than mLruProcessServiceStart.
- int cachedProcessPos = 0;
- for (int i = 0; i < lruProcessServiceStart
- && cachedProcessPos < scoredProcessRecords.length; ++i) {
- ProcessRecord app = lruList.get(i);
+ int numProcessesReRanked = 0;
+ while (numProcessesEvaluated < lruProcessServiceStart
+ && numProcessesReRanked < scoredProcessRecords.length) {
+ ProcessRecord process = lruList.get(numProcessesEvaluated);
// Processes that will be assigned a cached oom adj score.
- if (!app.isKilledByAm() && app.getThread() != null && app.mState.getCurAdj()
- >= ProcessList.UNKNOWN_ADJ) {
- scoredProcessRecords[cachedProcessPos].proc = app;
- scoredProcessRecords[cachedProcessPos].score = 0.0f;
- lruPositions[cachedProcessPos] = i;
- ++cachedProcessPos;
+ if (appCanBeReRanked(process)) {
+ scoredProcessRecords[numProcessesReRanked].proc = process;
+ scoredProcessRecords[numProcessesReRanked].score = 0.0f;
+ lruPositions[numProcessesReRanked] = numProcessesEvaluated;
+ ++numProcessesReRanked;
+ }
+ ++numProcessesEvaluated;
+ }
+
+ // Count how many apps we're not re-ranking (up to preserveTopNApps).
+ int numProcessesNotReRanked = 0;
+ while (numProcessesEvaluated < lruProcessServiceStart
+ && numProcessesNotReRanked < preserveTopNApps) {
+ ProcessRecord process = lruList.get(numProcessesEvaluated);
+ if (appCanBeReRanked(process)) {
+ numProcessesNotReRanked++;
+ }
+ numProcessesEvaluated++;
+ }
+ // Exclude the top `preserveTopNApps` apps from re-ranking.
+ if (numProcessesNotReRanked < preserveTopNApps) {
+ numProcessesReRanked -= preserveTopNApps - numProcessesNotReRanked;
+ if (numProcessesReRanked < 0) {
+ numProcessesReRanked = 0;
}
}
- // TODO maybe ensure a certain number above this in the cache before re-ranking.
- if (cachedProcessPos < scoredProcessRecords.length) {
- // Ignore we don't have enough processes to worry about re-ranking.
- return;
+ if (useFrequentRss) {
+ // Update RSS values for re-ranked apps.
+ long nowMs = SystemClock.elapsedRealtime();
+ for (int i = 0; i < numProcessesReRanked; ++i) {
+ RankedProcessRecord scoredProcessRecord = scoredProcessRecords[i];
+ long sinceUpdateMs =
+ nowMs - scoredProcessRecord.proc.mState.getCacheOomRankerRssTimeMs();
+ if (scoredProcessRecord.proc.mState.getCacheOomRankerRss() != 0
+ && sinceUpdateMs < rssUpdateRateMs) {
+ continue;
+ }
+
+ long[] rss = mProcessDependencies.getRss(scoredProcessRecord.proc.getPid());
+ if (rss == null || rss.length == 0) {
+ Slog.e(
+ OomAdjuster.TAG,
+ "Process.getRss returned bad value, not re-ranking: "
+ + Arrays.toString(rss));
+ return;
+ }
+ // First element is total RSS:
+ // frameworks/base/core/jni/android_util_Process.cpp:1192
+ scoredProcessRecord.proc.mState.setCacheOomRankerRss(rss[0], nowMs);
+ scoredProcessRecord.proc.mProfile.setLastRss(rss[0]);
+ }
}
// Add scores for each of the weighted features we want to rank based on.
if (lruWeight > 0.0f) {
// This doesn't use the LRU list ordering as after the first re-ranking
// that will no longer be lru.
- Arrays.sort(scoredProcessRecords, LAST_ACTIVITY_TIME_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked,
+ LAST_ACTIVITY_TIME_COMPARATOR);
addToScore(scoredProcessRecords, lruWeight);
}
if (rssWeight > 0.0f) {
- synchronized (mService.mAppProfiler.mProfilerLock) {
- Arrays.sort(scoredProcessRecords, LAST_RSS_COMPARATOR);
+ if (useFrequentRss) {
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, RSS_COMPARATOR);
+ } else {
+ synchronized (mService.mAppProfiler.mProfilerLock) {
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, LAST_RSS_COMPARATOR);
+ }
}
addToScore(scoredProcessRecords, rssWeight);
}
if (usesWeight > 0.0f) {
- Arrays.sort(scoredProcessRecords, CACHE_USE_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, CACHE_USE_COMPARATOR);
addToScore(scoredProcessRecords, usesWeight);
}
// Re-rank by the new combined score.
- Arrays.sort(scoredProcessRecords, SCORED_PROCESS_RECORD_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked,
+ SCORED_PROCESS_RECORD_COMPARATOR);
if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
boolean printedHeader = false;
- for (int i = 0; i < scoredProcessRecords.length; ++i) {
+ for (int i = 0; i < numProcessesReRanked; ++i) {
if (scoredProcessRecords[i].proc.getPid()
!= lruList.get(lruPositions[i]).getPid()) {
if (!printedHeader) {
@@ -260,12 +410,18 @@
}
}
- for (int i = 0; i < scoredProcessRecords.length; ++i) {
+ for (int i = 0; i < numProcessesReRanked; ++i) {
lruList.set(lruPositions[i], scoredProcessRecords[i].proc);
scoredProcessRecords[i].proc = null;
}
}
+ private static boolean appCanBeReRanked(ProcessRecord process) {
+ return !process.isKilledByAm()
+ && process.getThread() != null
+ && process.mState.getCurAdj() >= ProcessList.UNKNOWN_ADJ;
+ }
+
private static void addToScore(RankedProcessRecord[] scores, float weight) {
for (int i = 1; i < scores.length; ++i) {
scores[i].score += i * weight;
@@ -305,6 +461,16 @@
}
}
+ private static class RssComparator implements Comparator<RankedProcessRecord> {
+ @Override
+ public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
+ // High RSS first to match least recently used.
+ return Long.compare(
+ o2.proc.mState.getCacheOomRankerRss(),
+ o1.proc.mState.getCacheOomRankerRss());
+ }
+ }
+
private static class LastRssComparator implements Comparator<RankedProcessRecord> {
@Override
public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
@@ -317,4 +483,18 @@
public ProcessRecord proc;
public float score;
}
+
+ /**
+ * Interface for mocking {@link Process} static methods.
+ */
+ interface ProcessDependencies {
+ long[] getRss(int pid);
+ }
+
+ private static class ProcessDependenciesImpl implements ProcessDependencies {
+ @Override
+ public long[] getRss(int pid) {
+ return Process.getRss(pid);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 5c9d385..2e3e635 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -94,8 +94,6 @@
sGlobalSettingToTypeMap.put(
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
sGlobalSettingToTypeMap.put(
- Settings.Global.ANGLE_ALLOWLIST, String.class);
- sGlobalSettingToTypeMap.put(
Settings.Global.ANGLE_EGL_FEATURES, String.class);
sGlobalSettingToTypeMap.put(
Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5c19ceb..ff480d1 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,6 +72,7 @@
import static com.android.server.am.ProcessList.TAG_PROCESS_OBSERVERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -114,6 +115,8 @@
import com.android.server.wm.WindowProcessController;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -171,6 +174,27 @@
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
+ static final int CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY = 0;
+ static final int CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY = 1;
+ static final int CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME = 2;
+
+ @IntDef(prefix = { "CACHED_COMPAT_CHANGE_" }, value = {
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY,
+ CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY,
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ static @interface CachedCompatChangeId{}
+
+ /**
+ * Mapping from CACHED_COMPAT_CHANGE_* to the actual compat change id.
+ */
+ static final long[] CACHED_COMPAT_CHANGE_IDS_MAPPING = new long[] {
+ PROCESS_CAPABILITY_CHANGE_ID,
+ CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
+ USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+ };
+
/**
* For some direct access we need to power manager.
*/
@@ -368,6 +392,12 @@
return mPlatformCompatCache;
}
+ boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, ApplicationInfo app,
+ boolean defaultValue) {
+ return getPlatformCompatCache().isChangeEnabled(
+ CACHED_COMPAT_CHANGE_IDS_MAPPING[cachedCompatChangeId], app, defaultValue);
+ }
+
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
this(service, processList, activeUids, createAdjusterThread());
}
@@ -1929,12 +1959,8 @@
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
- boolean enabled = false;
- try {
- enabled = getPlatformCompatCache().isChangeEnabled(
- CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID, s.appInfo);
- } catch (RemoteException e) {
- }
+ final boolean enabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY);
if (enabled) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
@@ -2151,12 +2177,8 @@
// to client's state.
clientProcState = PROCESS_STATE_BOUND_TOP;
state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
- boolean enabled = false;
- try {
- enabled = getPlatformCompatCache().isChangeEnabled(
- PROCESS_CAPABILITY_CHANGE_ID, client.info);
- } catch (RemoteException e) {
- }
+ final boolean enabled = cstate.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
if (enabled) {
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
// TOP process passes all capabilities to the service.
@@ -2800,8 +2822,8 @@
state.setProcStateChanged(true);
}
} else if (state.hasReportedInteraction()) {
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
final long interactionThreshold = fgsInteractionChangeEnabled
? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
: mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
@@ -2811,8 +2833,8 @@
maybeUpdateUsageStatsLSP(app, nowElapsed);
}
} else {
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
final long interactionThreshold = fgsInteractionChangeEnabled
? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
: mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
@@ -2901,8 +2923,8 @@
if (mService.mUsageStatsService == null) {
return;
}
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
boolean isInteraction;
// To avoid some abuse patterns, we are going to be careful about what we consider
// to be an app interaction. Being the top activity doesn't count while the display
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index dc6bcd8..206dd88 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -21,6 +21,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.OomAdjuster.CachedCompatChangeId;
import static com.android.server.am.ProcessRecord.TAG;
import android.annotation.ElapsedRealtimeLong;
@@ -342,6 +343,20 @@
private int mCacheOomRankerUseCount;
/**
+ * Process memory usage (RSS).
+ *
+ * Periodically populated by {@code CacheOomRanker}, stored in this object to cache the values.
+ */
+ @GuardedBy("mService")
+ private long mCacheOomRankerRss;
+
+ /**
+ * The last time, in milliseconds since boot, since {@link #mCacheOomRankerRss} was updated.
+ */
+ @GuardedBy("mService")
+ private long mCacheOomRankerRssTimeMs;
+
+ /**
* Whether or not this process is reachable from given process.
*/
@GuardedBy("mService")
@@ -377,6 +392,16 @@
@GuardedBy("mService")
private int mCachedIsReceivingBroadcast = VALUE_INVALID;
+ /**
+ * Cache the return value of PlatformCompat.isChangeEnabled().
+ */
+ @GuardedBy("mService")
+ private int[] mCachedCompatChanges = new int[] {
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME
+ };
+
@GuardedBy("mService")
private int mCachedAdj = ProcessList.INVALID_ADJ;
@GuardedBy("mService")
@@ -566,6 +591,10 @@
@GuardedBy({"mService", "mProcLock"})
void setSetProcState(int setProcState) {
+ if (ActivityManager.isProcStateCached(mSetProcState)
+ && !ActivityManager.isProcStateCached(setProcState)) {
+ mCacheOomRankerUseCount++;
+ }
mSetProcState = setProcState;
}
@@ -829,12 +858,7 @@
@GuardedBy("mService")
void setCached(boolean cached) {
- if (mCached != cached) {
- mCached = cached;
- if (cached) {
- ++mCacheOomRankerUseCount;
- }
- }
+ mCached = cached;
}
@GuardedBy("mService")
@@ -1009,6 +1033,16 @@
}
@GuardedBy("mService")
+ boolean getCachedCompatChange(@CachedCompatChangeId int cachedCompatChangeId) {
+ if (mCachedCompatChanges[cachedCompatChangeId] == VALUE_INVALID) {
+ mCachedCompatChanges[cachedCompatChangeId] = mService.mOomAdjuster
+ .isChangeEnabled(cachedCompatChangeId, mApp.info, false /* default */)
+ ? VALUE_TRUE : VALUE_FALSE;
+ }
+ return mCachedCompatChanges[cachedCompatChangeId] == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
int adj, boolean foregroundActivities, boolean hasVisibleActivities, int procState,
int schedGroup, int appUid, int logUid, int processCurTop) {
@@ -1088,6 +1122,9 @@
mCurSchedGroup = mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
mCurProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
PROCESS_STATE_NONEXISTENT;
+ for (int i = 0; i < mCachedCompatChanges.length; i++) {
+ mCachedCompatChanges[i] = VALUE_INVALID;
+ }
}
@GuardedBy("mService")
@@ -1127,6 +1164,21 @@
return mLastInvisibleTime;
}
+ public void setCacheOomRankerRss(long rss, long rssTimeMs) {
+ mCacheOomRankerRss = rss;
+ mCacheOomRankerRssTimeMs = rssTimeMs;
+ }
+
+ @GuardedBy("mService")
+ public long getCacheOomRankerRss() {
+ return mCacheOomRankerRss;
+ }
+
+ @GuardedBy("mService")
+ public long getCacheOomRankerRssTimeMs() {
+ return mCacheOomRankerRssTimeMs;
+ }
+
@GuardedBy({"mService", "mProcLock"})
void dump(PrintWriter pw, String prefix, long nowUptime) {
if (mReportedInteraction || mFgInteractionTime != 0) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 804e442..17930ea 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -927,9 +927,9 @@
}
public void postNotification() {
- final int appUid = appInfo.uid;
- final int appPid = app.getPid();
- if (isForeground && foregroundNoti != null) {
+ if (isForeground && foregroundNoti != null && app != null) {
+ final int appUid = appInfo.uid;
+ final int appPid = app.getPid();
// Do asynchronous communication with notification manager to
// avoid deadlocks.
final String localPackageName = packageName;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index af8d7a6..3003c52 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -272,6 +272,13 @@
"com.android.graphics.intervention.wm.allowDownscale";
/**
+ * Metadata that can be included in the app manifest to allow/disallow any ANGLE
+ * interventions. Default value is TRUE.
+ */
+ public static final String METADATA_ANGLE_ALLOW_ANGLE =
+ "com.android.graphics.intervention.angle.allowAngle";
+
+ /**
* Metadata that needs to be included in the app manifest to OPT-IN to PERFORMANCE mode.
* This means the app will assume full responsibility for the experience provided by this
* mode and the system will enable no window manager downscaling.
@@ -294,6 +301,7 @@
private boolean mPerfModeOptedIn;
private boolean mBatteryModeOptedIn;
private boolean mAllowDownscale;
+ private boolean mAllowAngle;
GamePackageConfiguration(String packageName, int userId) {
mPackageName = packageName;
@@ -305,10 +313,12 @@
mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
+ mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
} else {
mPerfModeOptedIn = false;
mBatteryModeOptedIn = false;
mAllowDownscale = true;
+ mAllowAngle = true;
}
} catch (PackageManager.NameNotFoundException e) {
// Not all packages are installed, hence ignore those that are not installed yet.
@@ -340,14 +350,26 @@
public static final String MODE_KEY = "mode";
public static final String SCALING_KEY = "downscaleFactor";
public static final String DEFAULT_SCALING = "1.0";
+ public static final String ANGLE_KEY = "useAngle";
private final @GameMode int mGameMode;
private final String mScaling;
+ private final boolean mUseAngle;
GameModeConfiguration(KeyValueListParser parser) {
mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
- mScaling = !mAllowDownscale || isGameModeOptedIn(mGameMode)
+ // isGameModeOptedIn() returns if an app will handle all of the changes necessary
+ // for a particular game mode. If so, the Android framework (i.e.
+ // GameManagerService) will not do anything for the app (like window scaling or
+ // using ANGLE).
+ mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
+ // We only want to use ANGLE if:
+ // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
+ // - The app has not opted in to performing the work itself AND
+ // - The Phenotype config has enabled it.
+ mUseAngle = mAllowAngle && !willGamePerformOptimizations(mGameMode)
+ && parser.getBoolean(ANGLE_KEY, false);
}
public int getGameMode() {
@@ -358,6 +380,10 @@
return mScaling;
}
+ public boolean getUseAngle() {
+ return mUseAngle;
+ }
+
public boolean isValid() {
return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
|| mGameMode == GameManager.GAME_MODE_BATTERY)
@@ -368,7 +394,8 @@
* @hide
*/
public String toString() {
- return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]";
+ return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:"
+ + mUseAngle + "]";
}
/**
@@ -384,13 +411,14 @@
}
/**
- * Gets whether a package has opted into a game mode via its manifest.
+ * Returns if the app will assume full responsibility for the experience provided by this
+ * mode. If True, the system will not perform any interventions for the app.
*
* @return True if the app package has specified in its metadata either:
* "com.android.app.gamemode.performance.enabled" or
* "com.android.app.gamemode.battery.enabled" with a value of "true"
*/
- public boolean isGameModeOptedIn(@GameMode int gameMode) {
+ public boolean willGamePerformOptimizations(@GameMode int gameMode) {
return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
|| (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
}
@@ -631,7 +659,34 @@
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
}
- updateCompatModeDownscale(packageName, gameMode);
+ updateInterventions(packageName, gameMode);
+ }
+
+ /**
+ * Get if ANGLE is enabled for the package for the currently enabled game mode.
+ * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+ */
+ @Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+ public @GameMode boolean getAngleEnabled(String packageName, int userId)
+ throws SecurityException {
+ final int gameMode = getGameMode(packageName, userId);
+ if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+ return false;
+ }
+
+ synchronized (mDeviceConfigLock) {
+ final GamePackageConfiguration config = mConfigs.get(packageName);
+ if (config == null) {
+ return false;
+ }
+ GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
+ config.getGameModeConfiguration(gameMode);
+ if (gameModeConfiguration == null) {
+ return false;
+ }
+ return gameModeConfiguration.getUseAngle();
+ }
}
/**
@@ -753,7 +808,7 @@
if (DEBUG) {
Slog.v(TAG, dumpDeviceConfigs());
}
- if (packageConfig.isGameModeOptedIn(gameMode)) {
+ if (packageConfig.willGamePerformOptimizations(gameMode)) {
disableCompatScale(packageName);
return;
}
@@ -782,6 +837,17 @@
return (bitField & modeToBitmask(gameMode)) != 0;
}
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ private void updateUseAngle(String packageName, @GameMode int gameMode) {
+ // TODO (b/188475576): Nothing to do yet. Remove if it's still empty when we're ready to
+ // ship.
+ }
+
+ private void updateInterventions(String packageName, @GameMode int gameMode) {
+ updateCompatModeDownscale(packageName, gameMode);
+ updateUseAngle(packageName, gameMode);
+ }
+
/**
* @hide
*/
@@ -839,11 +905,11 @@
if (newGameMode != gameMode) {
setGameMode(packageName, newGameMode, userId);
}
- updateCompatModeDownscale(packageName, gameMode);
+ updateInterventions(packageName, gameMode);
}
}
} catch (Exception e) {
- Slog.e(TAG, "Failed to update compat modes for user: " + userId);
+ Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
}
}
@@ -851,7 +917,7 @@
final List<PackageInfo> packages =
mPackageManager.getInstalledPackagesAsUser(0, userId);
return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
- == ApplicationInfo.CATEGORY_GAME)
+ == ApplicationInfo.CATEGORY_GAME)
.map(e -> e.packageName)
.toArray(String[]::new);
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index db2ecc5..19dcee4 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -696,7 +696,7 @@
idpw.print("User Level Hibernation States, ");
idpw.printPair("user", userId);
idpw.println();
- Map<String, UserLevelState> stateMap = mUserStates.get(i);
+ Map<String, UserLevelState> stateMap = mUserStates.get(userId);
idpw.increaseIndent();
for (UserLevelState state : stateMap.values()) {
idpw.print(state);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 38f71ba..84a3060 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1812,10 +1812,10 @@
if (client == null) {
return;
}
- Log.w(TAG, "Speaker client died");
+ Log.w(TAG, "Communication client died");
setCommunicationRouteForClient(
- client.getBinder(), client.getPid(), null,
- BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
+ client.getBinder(), client.getPid(), null, BtHelper.SCO_MODE_UNDEFINED,
+ "onCommunicationRouteClientDied");
}
/**
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bf5f4c2..6e8e9f2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2596,18 +2596,19 @@
case KeyEvent.KEYCODE_VOLUME_UP:
adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, keyEventMode);
+ Binder.getCallingUid(), Binder.getCallingPid(), true, keyEventMode);
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, keyEventMode);
+ Binder.getCallingUid(), Binder.getCallingPid(), true, keyEventMode);
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, VOL_ADJUST_NORMAL);
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ true, VOL_ADJUST_NORMAL);
}
break;
default:
@@ -2620,8 +2621,8 @@
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
- caller, Binder.getCallingUid(), callingHasAudioSettingsPermission(),
- VOL_ADJUST_NORMAL);
+ caller, Binder.getCallingUid(), Binder.getCallingPid(),
+ callingHasAudioSettingsPermission(), VOL_ADJUST_NORMAL);
}
public void setNavigationRepeatSoundEffectsEnabled(boolean enabled) {
@@ -2647,7 +2648,7 @@
}
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
+ String callingPackage, String caller, int uid, int pid, boolean hasModifyAudioSettings,
int keyEventMode) {
if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+ ", flags=" + flags + ", caller=" + caller
@@ -2720,7 +2721,7 @@
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
- adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid,
+ adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, pid,
hasModifyAudioSettings, keyEventMode);
}
@@ -2752,12 +2753,12 @@
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
- Binder.getCallingUid(), callingHasAudioSettingsPermission(),
- VOL_ADJUST_NORMAL);
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ callingHasAudioSettingsPermission(), VOL_ADJUST_NORMAL);
}
protected void adjustStreamVolume(int streamType, int direction, int flags,
- String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
+ String callingPackage, String caller, int uid, int pid, boolean hasModifyAudioSettings,
int keyEventMode) {
if (mUseFixedVolume) {
return;
@@ -2779,8 +2780,7 @@
if (isMuteAdjust &&
(streamType == AudioSystem.STREAM_VOICE_CALL ||
streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_PHONE_STATE)
+ mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
@@ -2790,8 +2790,8 @@
// If the stream is STREAM_ASSISTANT,
// make sure that the calling app have the MODIFY_AUDIO_ROUTING permission.
if (streamType == AudioSystem.STREAM_ASSISTANT &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ mContext.checkPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
@@ -2823,8 +2823,8 @@
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ // validate calling package and app op
+ if (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {
return;
}
@@ -3547,8 +3547,7 @@
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {
return;
}
@@ -3976,20 +3975,19 @@
}
private void setMasterMuteInternal(boolean mute, int flags, String callingPackage, int uid,
- int userId) {
+ int userId, int pid) {
// If we are being called by the system check for user we are going to change
// so we handle user restrictions correctly.
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
}
// If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting.
- if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!mute && !checkNoteAppOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)) {
return;
}
if (userId != UserHandle.getCallingUserId() &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
@@ -4029,7 +4027,7 @@
public void setMasterMute(boolean mute, int flags, String callingPackage, int userId) {
enforceModifyAudioRoutingPermission();
setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid(),
- userId);
+ userId, Binder.getCallingPid());
}
/** @see AudioManager#getStreamVolume(int) */
@@ -4115,8 +4113,7 @@
? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE);
// If OP_MUTE_MICROPHONE is set, disallow unmuting.
- if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!on && !checkNoteAppOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "disallow unmuting").record();
return;
}
@@ -4931,8 +4928,8 @@
// direction and stream type swap here because the public
// adjustSuggested has a different order than the other methods.
- adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName, uid,
- hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
+ adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName,
+ uid, pid, hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
}
/** @see AudioManager#adjustStreamVolumeForUid(int, int, int, String, int, int, int) */
@@ -4951,7 +4948,7 @@
.toString()));
}
- adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid,
+ adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid, pid,
hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
}
@@ -5295,6 +5292,10 @@
// TODO investigate internal users due to deprecation of SDK API
/** @see AudioManager#setBluetoothA2dpOn(boolean) */
public void setBluetoothA2dpOn(boolean on) {
+ if (!checkAudioSettingsPermission("setBluetoothA2dpOn()")) {
+ return;
+ }
+
// for logging only
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -5320,6 +5321,10 @@
/** @see AudioManager#startBluetoothSco() */
public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+ if (!checkAudioSettingsPermission("startBluetoothSco()")) {
+ return;
+ }
+
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final int scoAudioMode =
@@ -5342,6 +5347,10 @@
/** @see AudioManager#startBluetoothScoVirtualCall() */
public void startBluetoothScoVirtualCall(IBinder cb) {
+ if (!checkAudioSettingsPermission("startBluetoothScoVirtualCall()")) {
+ return;
+ }
+
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
@@ -10554,4 +10563,31 @@
}
mFullVolumeDevices.remove(audioSystemDeviceOut);
}
+
+ //====================
+ // Helper functions for app ops
+ //====================
+ /**
+ * Validates, and notes an app op for a given uid and package name.
+ * Validation comes from exception catching: a security exception indicates the package
+ * doesn't exist, an IAE indicates the uid and package don't match. The code only checks
+ * if exception was thrown for robustness to code changes in op validation
+ * @param op the app op to check
+ * @param uid the uid of the caller
+ * @param packageName the package to check
+ * @return true if the origin of the call is valid (no uid / package mismatch) and the caller
+ * is allowed to perform the operation
+ */
+ private boolean checkNoteAppOp(int op, int uid, String packageName) {
+ try {
+ if (mAppOps.noteOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error noting op:" + op + " on uid:" + uid + " for package:"
+ + packageName, e);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index bb627e5..00cb280 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -36,7 +36,16 @@
public static final String TAG = "AudioService.FadeOutManager";
+ /** duration of the fade out curve */
/*package*/ static final long FADE_OUT_DURATION_MS = 2000;
+ /**
+ * delay after which a faded out player will be faded back in. This will be heard by the user
+ * only in the case of unmuting players that didn't respect audio focus and didn't stop/pause
+ * when their app lost focus.
+ * This is the amount of time between the app being notified of
+ * the focus loss (when its muted by the fade out), and the time fade in (to unmute) starts
+ */
+ /*package*/ static final long DELAY_FADE_IN_OFFENDERS_MS = 2000;
private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG;
@@ -148,6 +157,11 @@
}
}
+ /**
+ * Remove the app for the given UID from the list of faded out apps, unfade out its players
+ * @param uid the uid for the app to unfade out
+ * @param players map of current available players (so we can get an APC from piid)
+ */
synchronized void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
Log.i(TAG, "unfadeOutUid() uid:" + uid);
final FadedOutApp fa = mFadedApps.remove(uid);
@@ -157,12 +171,6 @@
fa.removeUnfadeAll(players);
}
- synchronized void forgetUid(int uid) {
- //Log.v(TAG, "forget() uid:" + uid);
- //mFadedApps.remove(uid);
- // TODO unfade all players later in case they are reused or the app continued to play
- }
-
// pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
// see {@link PlaybackActivityMonitor#playerEvent}
synchronized void checkFade(@NonNull AudioPlaybackConfiguration apc) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index e6c4abfa..9548ada 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -131,6 +131,11 @@
@Override
public void restoreVShapedPlayers(@NonNull FocusRequester winner) {
mFocusEnforcer.restoreVShapedPlayers(winner);
+ // remove scheduled events to unfade out offending players (if any) corresponding to
+ // this uid, as we're removing any effects of muting/ducking/fade out now
+ mFocusHandler.removeEqualMessages(MSL_L_FORGET_UID,
+ new ForgetFadeUidInfo(winner.getClientUid()));
+
}
@Override
@@ -1182,6 +1187,13 @@
mFocusHandler.obtainMessage(MSG_L_FOCUS_LOSS_AFTER_FADE, focusLoser),
FadeOutManager.FADE_OUT_DURATION_MS);
}
+
+ private void postForgetUidLater(int uid) {
+ mFocusHandler.sendMessageDelayed(
+ mFocusHandler.obtainMessage(MSL_L_FORGET_UID, new ForgetFadeUidInfo(uid)),
+ FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS);
+ }
+
//=================================================================
// Message handling
private Handler mFocusHandler;
@@ -1196,6 +1208,8 @@
*/
private static final int MSG_L_FOCUS_LOSS_AFTER_FADE = 1;
+ private static final int MSL_L_FORGET_UID = 2;
+
private void initFocusThreading() {
mFocusThread = new HandlerThread(TAG);
mFocusThread.start();
@@ -1213,15 +1227,56 @@
if (loser.isInFocusLossLimbo()) {
loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
loser.release();
- mFocusEnforcer.forgetUid(loser.getClientUid());
+ postForgetUidLater(loser.getClientUid());
}
}
break;
+
+ case MSL_L_FORGET_UID:
+ final int uid = ((ForgetFadeUidInfo) msg.obj).mUid;
+ if (DEBUG) {
+ Log.d(TAG, "MSL_L_FORGET_UID uid=" + uid);
+ }
+ mFocusEnforcer.forgetUid(uid);
+ break;
default:
break;
}
}
};
+ }
+ /**
+ * Class to associate a UID with a scheduled event to "forget" a UID for the fade out behavior.
+ * Having a class with an equals() override allows using Handler.removeEqualsMessage() to
+ * unschedule events when needed. Here we need to unschedule the "unfading out" == "forget uid"
+ * whenever a new, more recent, focus related event happens before this one is handled.
+ */
+ private static final class ForgetFadeUidInfo {
+ private final int mUid;
+
+ ForgetFadeUidInfo(int uid) {
+ mUid = uid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ForgetFadeUidInfo f = (ForgetFadeUidInfo) o;
+ if (f.mUid != mUid) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mUid;
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index a13b2eb..b94cea4 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -747,7 +747,11 @@
@Override
public void forgetUid(int uid) {
- mFadingManager.forgetUid(uid);
+ final HashMap<Integer, AudioPlaybackConfiguration> players;
+ synchronized (mPlayerLock) {
+ players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone();
+ }
+ mFadingManager.unfadeOutUid(uid, players);
}
//=================================================================
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 0cd2e3d..b42f898 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -206,7 +206,7 @@
}
@Override
- public void authenticate(IBinder token, long sessionId, int userId,
+ public long authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo)
throws RemoteException {
// Only allow internal clients to authenticate with a different userId.
@@ -223,18 +223,18 @@
if (!checkAppOps(callingUid, opPackageName, "authenticate()")) {
authenticateFastFail("Denied by app ops: " + opPackageName, receiver);
- return;
+ return -1;
}
if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
authenticateFastFail(
"Unable to authenticate, one or more null arguments", receiver);
- return;
+ return -1;
}
if (!Utils.isForeground(callingUid, callingPid)) {
authenticateFastFail("Caller is not foreground: " + opPackageName, receiver);
- return;
+ return -1;
}
if (promptInfo.containsTestConfigurations()) {
@@ -251,7 +251,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- mBiometricService.authenticate(
+ return mBiometricService.authenticate(
token, sessionId, userId, receiver, opPackageName, promptInfo);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -270,7 +270,7 @@
}
@Override
- public void cancelAuthentication(IBinder token, String opPackageName)
+ public void cancelAuthentication(IBinder token, String opPackageName, long requestId)
throws RemoteException {
checkPermission();
@@ -281,7 +281,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- mBiometricService.cancelAuthentication(token, opPackageName);
+ mBiometricService.cancelAuthentication(token, opPackageName, requestId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index bdde980..0da6a1b 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -128,6 +128,7 @@
@VisibleForTesting final IBinder mToken;
// Info to be shown on BiometricDialog when all cookies are returned.
@VisibleForTesting final PromptInfo mPromptInfo;
+ private final long mRequestId;
private final long mOperationId;
private final int mUserId;
private final IBiometricSensorReceiver mSensorReceiver;
@@ -142,6 +143,8 @@
private @BiometricMultiSensorMode int mMultiSensorMode;
private @MultiSensorState int mMultiSensorState;
private int[] mSensors;
+ // TODO(b/197265902): merge into state
+ private boolean mCancelled;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
private byte[] mTokenEscrow;
@@ -162,6 +165,7 @@
@NonNull ClientDeathReceiver clientDeathReceiver,
@NonNull PreAuthInfo preAuthInfo,
@NonNull IBinder token,
+ long requestId,
long operationId,
int userId,
@NonNull IBiometricSensorReceiver sensorReceiver,
@@ -179,6 +183,7 @@
mClientDeathReceiver = clientDeathReceiver;
mPreAuthInfo = preAuthInfo;
mToken = token;
+ mRequestId = requestId;
mOperationId = operationId;
mUserId = userId;
mSensorReceiver = sensorReceiver;
@@ -187,6 +192,7 @@
mPromptInfo = promptInfo;
mDebugEnabled = debugEnabled;
mFingerprintSensorProperties = fingerprintSensorProperties;
+ mCancelled = false;
try {
mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -233,7 +239,7 @@
Slog.v(TAG, "waiting for cooking for sensor: " + sensor.id);
}
sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
- mUserId, mSensorReceiver, mOpPackageName, cookie,
+ mUserId, mSensorReceiver, mOpPackageName, mRequestId, cookie,
mPromptInfo.isAllowBackgroundAuthentication());
}
}
@@ -255,8 +261,9 @@
true /* credentialAllowed */,
false /* requireConfirmation */,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
} else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
// Some combination of biometric or biometric|credential is requested
@@ -270,6 +277,11 @@
}
void onCookieReceived(int cookie) {
+ if (mCancelled) {
+ Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie);
+ return;
+ }
+
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
sensor.goToStateCookieReturnedIfCookieMatches(cookie);
}
@@ -301,8 +313,9 @@
mPreAuthInfo.shouldShowCredential(),
requireConfirmation,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
mState = STATE_AUTH_STARTED;
} catch (RemoteException e) {
@@ -369,7 +382,7 @@
final boolean shouldCancel = filter.apply(sensor);
Slog.d(TAG, "sensorId: " + sensor.id + ", shouldCancel: " + shouldCancel);
if (shouldCancel) {
- sensor.goToStateCancelling(mToken, mOpPackageName);
+ sensor.goToStateCancelling(mToken, mOpPackageName, mRequestId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
@@ -425,8 +438,9 @@
true /* credentialAllowed */,
false /* requireConfirmation */,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
} else {
mClientReceiver.onError(modality, error, vendorCode);
@@ -775,6 +789,8 @@
* @return true if this AuthSession is finished, e.g. should be set to null
*/
boolean onCancelAuthSession(boolean force) {
+ mCancelled = true;
+
final boolean authStarted = mState == STATE_AUTH_CALLED
|| mState == STATE_AUTH_STARTED
|| mState == STATE_AUTH_STARTED_UI_SHOWING;
@@ -820,6 +836,7 @@
return Utils.isCredentialRequested(mPromptInfo);
}
+ @VisibleForTesting
boolean allCookiesReceived() {
final int remainingCookies = mPreAuthInfo.numSensorsWaitingForCookie();
Slog.d(TAG, "Remaining cookies: " + remainingCookies);
@@ -839,6 +856,10 @@
return mState;
}
+ long getRequestId() {
+ return mRequestId;
+ }
+
private int statsModality() {
int modality = 0;
@@ -901,7 +922,9 @@
@Override
public String toString() {
return "State: " + mState
+ + ", cancelled: " + mCancelled
+ ", isCrypto: " + isCrypto()
- + ", PreAuthInfo: " + mPreAuthInfo;
+ + ", PreAuthInfo: " + mPreAuthInfo
+ + ", requestId: " + mRequestId;
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 8a842b5..0333c3e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -108,11 +108,11 @@
void goToStateWaitingForCookie(boolean requireConfirmation, IBinder token, long sessionId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication)
+ long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mCookie = cookie;
impl.prepareForAuthentication(requireConfirmation, token,
- sessionId, userId, sensorReceiver, opPackageName, mCookie,
+ sessionId, userId, sensorReceiver, opPackageName, requestId, mCookie,
allowBackgroundAuthentication);
mSensorState = STATE_WAITING_FOR_COOKIE;
}
@@ -129,8 +129,9 @@
mSensorState = STATE_AUTHENTICATING;
}
- void goToStateCancelling(IBinder token, String opPackageName) throws RemoteException {
- impl.cancelAuthenticationFromService(token, opPackageName);
+ void goToStateCancelling(IBinder token, String opPackageName, long requestId)
+ throws RemoteException {
+ impl.cancelAuthenticationFromService(token, opPackageName, requestId);
mSensorState = STATE_CANCELING;
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index b1d300c..e0775d4 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -83,6 +83,7 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
@@ -115,6 +116,7 @@
final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
private final Random mRandom = new Random();
+ @NonNull private final AtomicLong mRequestCounter;
@VisibleForTesting
IStatusBarService mStatusBarService;
@@ -194,6 +196,7 @@
SomeArgs args = (SomeArgs) msg.obj;
handleAuthenticate(
(IBinder) args.arg1 /* token */,
+ (long) args.arg6 /* requestId */,
(long) args.arg2 /* operationId */,
args.argi1 /* userid */,
(IBiometricServiceReceiver) args.arg3 /* receiver */,
@@ -204,7 +207,9 @@
}
case MSG_CANCEL_AUTHENTICATION: {
- handleCancelAuthentication();
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleCancelAuthentication((long) args.arg3 /* requestId */);
+ args.recycle();
break;
}
@@ -683,13 +688,13 @@
}
@Override // Binder call
- public void authenticate(IBinder token, long operationId, int userId,
+ public long authenticate(IBinder token, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
checkInternalPermission();
if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
Slog.e(TAG, "Unable to authenticate, one or more null arguments");
- return;
+ return -1;
}
if (!Utils.isValidAuthenticatorConfig(promptInfo)) {
@@ -706,6 +711,8 @@
}
}
+ final long requestId = mRequestCounter.incrementAndGet();
+
SomeArgs args = SomeArgs.obtain();
args.arg1 = token;
args.arg2 = operationId;
@@ -713,15 +720,23 @@
args.arg3 = receiver;
args.arg4 = opPackageName;
args.arg5 = promptInfo;
+ args.arg6 = requestId;
mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
+
+ return requestId;
}
@Override // Binder call
- public void cancelAuthentication(IBinder token, String opPackageName) {
+ public void cancelAuthentication(IBinder token, String opPackageName, long requestId) {
checkInternalPermission();
- mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = token;
+ args.arg2 = opPackageName;
+ args.arg3 = requestId;
+
+ mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
}
@Override // Binder call
@@ -1111,6 +1126,10 @@
return Settings.Secure.getInt(context.getContentResolver(),
CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
}
+
+ public AtomicLong getRequestGenerator() {
+ return new AtomicLong(0);
+ }
}
/**
@@ -1136,6 +1155,7 @@
mEnabledOnKeyguardCallbacks = new ArrayList<>();
mSettingObserver = mInjector.getSettingObserver(context, mHandler,
mEnabledOnKeyguardCallbacks);
+ mRequestCounter = mInjector.getRequestGenerator();
// TODO(b/193089985) This logic lives here (outside of CoexCoordinator) so that it doesn't
// need to depend on context. We can remove this code once the advanced logic is enabled
@@ -1349,7 +1369,7 @@
mCurrentAuthSession.onCookieReceived(cookie);
}
- private void handleAuthenticate(IBinder token, long operationId, int userId,
+ private void handleAuthenticate(IBinder token, long requestId, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
mHandler.post(() -> {
try {
@@ -1360,7 +1380,8 @@
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first
- + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo);
+ + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
+ + " requestId: " + requestId);
if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
@@ -1372,8 +1393,8 @@
promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
}
- authenticateInternal(token, operationId, userId, receiver, opPackageName,
- promptInfo, preAuthInfo);
+ authenticateInternal(token, requestId, operationId, userId, receiver,
+ opPackageName, promptInfo, preAuthInfo);
} else {
receiver.onError(preAuthStatus.first /* modality */,
preAuthStatus.second /* errorCode */,
@@ -1394,7 +1415,7 @@
* Note that this path is NOT invoked when the BiometricPrompt "Try again" button is pressed.
* In that case, see {@link #handleOnTryAgainPressed()}.
*/
- private void authenticateInternal(IBinder token, long operationId, int userId,
+ private void authenticateInternal(IBinder token, long requestId, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo,
PreAuthInfo preAuthInfo) {
Slog.d(TAG, "Creating authSession with authRequest: " + preAuthInfo);
@@ -1412,9 +1433,9 @@
final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
mCurrentAuthSession = new AuthSession(getContext(), mStatusBarService, mSysuiReceiver,
- mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, operationId, userId,
- mBiometricSensorReceiver, receiver, opPackageName, promptInfo, debugEnabled,
- mInjector.getFingerprintSensorProperties(getContext()));
+ mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, requestId,
+ operationId, userId, mBiometricSensorReceiver, receiver, opPackageName, promptInfo,
+ debugEnabled, mInjector.getFingerprintSensorProperties(getContext()));
try {
mCurrentAuthSession.goToInitialState();
} catch (RemoteException e) {
@@ -1422,11 +1443,21 @@
}
}
- private void handleCancelAuthentication() {
+ private void handleCancelAuthentication(long requestId) {
if (mCurrentAuthSession == null) {
Slog.e(TAG, "handleCancelAuthentication: AuthSession is null");
return;
}
+ if (mCurrentAuthSession.getRequestId() != requestId) {
+ // TODO: actually cancel the operation
+ // This can happen if the operation has been queued, but is cancelled before
+ // it reaches the head of the scheduler. Consider it a programming error for now
+ // and ignore it.
+ Slog.e(TAG, "handleCancelAuthentication: AuthSession mismatch current requestId: "
+ + mCurrentAuthSession.getRequestId() + " cancel for: " + requestId
+ + " (ignoring cancellation)");
+ return;
+ }
final boolean finished = mCurrentAuthSession.onCancelAuthSession(false /* force */);
if (finished) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 3eb6f4a..9764a16 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -26,6 +26,8 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.List;
import java.util.NoSuchElementException;
/**
@@ -70,26 +72,32 @@
}
/** Holder for wrapping multiple handlers into a single Callback. */
- protected static class CompositeCallback implements Callback {
+ public static class CompositeCallback implements Callback {
@NonNull
- private final Callback[] mCallbacks;
+ private final List<Callback> mCallbacks;
public CompositeCallback(@NonNull Callback... callbacks) {
- mCallbacks = callbacks;
+ mCallbacks = new ArrayList<>();
+
+ for (Callback callback : callbacks) {
+ if (callback != null) {
+ mCallbacks.add(callback);
+ }
+ }
}
@Override
public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- for (int i = 0; i < mCallbacks.length; i++) {
- mCallbacks[i].onClientStarted(clientMonitor);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onClientStarted(clientMonitor);
}
}
@Override
public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- for (int i = mCallbacks.length - 1; i >= 0; i--) {
- mCallbacks[i].onClientFinished(clientMonitor, success);
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).onClientFinished(clientMonitor, success);
}
}
}
@@ -101,6 +109,7 @@
private final int mSensorId; // sensorId as configured by the framework
@Nullable private IBinder mToken;
+ private long mRequestId;
@Nullable private ClientMonitorCallbackConverter mListener;
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
@@ -154,6 +163,7 @@
mSequentialId = sCount++;
mContext = context;
mToken = token;
+ mRequestId = -1;
mListener = listener;
mTargetUserId = userId;
mOwner = owner;
@@ -254,10 +264,33 @@
return mToken;
}
- public final int getSensorId() {
+ public int getSensorId() {
return mSensorId;
}
+ /** Unique request id. */
+ public final long getRequestId() {
+ return mRequestId;
+ }
+
+ /** If a unique id has been set via {@link #setRequestId(long)} */
+ public final boolean hasRequestId() {
+ return mRequestId > 0;
+ }
+
+ /**
+ * A unique identifier used to tie this operation to a request (i.e an API invocation).
+ *
+ * Subclasses should not call this method if this operation does not have a direct
+ * correspondence to a request and {@link #hasRequestId()} will return false.
+ */
+ protected final void setRequestId(long id) {
+ if (id <= 0) {
+ throw new IllegalArgumentException("request id must be positive");
+ }
+ mRequestId = id;
+ }
+
@VisibleForTesting
public Callback getCallback() {
return mCallback;
@@ -270,6 +303,7 @@
+ ", proto=" + getProtoEnum()
+ ", owner=" + getOwnerString()
+ ", cookie=" + getCookie()
+ + ", requestId=" + getRequestId()
+ ", userId=" + getTargetUserId() + "}";
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index feb9e2a..361ec40 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -643,22 +643,18 @@
/**
* Requests to cancel authentication or detection.
* @param token from the caller, should match the token passed in when requesting authentication
+ * @param requestId the id returned when requesting authentication
*/
- public void cancelAuthenticationOrDetection(IBinder token) {
- if (mCurrentOperation == null) {
- Slog.e(getTag(), "Unable to cancel authentication, null operation");
- return;
- }
- final boolean isCorrectClient = isAuthenticationOrDetectionOperation(mCurrentOperation);
- final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
+ public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
+ Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
+ + " current: " + mCurrentOperation
+ + " stack size: " + mPendingOperations.size());
- Slog.d(getTag(), "cancelAuthenticationOrDetection, isCorrectClient: " + isCorrectClient
- + ", tokenMatches: " + tokenMatches);
-
- if (isCorrectClient && tokenMatches) {
+ if (mCurrentOperation != null
+ && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
- } else if (!isCorrectClient) {
+ } else {
// Look through the current queue for all authentication clients for the specified
// token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
// all of them, instead of just the first one, since the API surface currently doesn't
@@ -666,8 +662,7 @@
// process. However, this generally does not happen anyway, and would be a class of
// bugs on its own.
for (Operation operation : mPendingOperations) {
- if (isAuthenticationOrDetectionOperation(operation)
- && operation.mClientMonitor.getToken() == token) {
+ if (canCancelAuthOperation(operation, token, requestId)) {
Slog.d(getTag(), "Marking " + operation
+ " as STATE_WAITING_IN_QUEUE_CANCELING");
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
@@ -676,10 +671,26 @@
}
}
- private boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
- final boolean isAuthentication = operation.mClientMonitor
- instanceof AuthenticationConsumer;
- final boolean isDetection = operation.mClientMonitor instanceof DetectionConsumer;
+ private static boolean canCancelAuthOperation(Operation operation, IBinder token,
+ long requestId) {
+ // TODO: restrict callers that can cancel without requestId (negative value)?
+ return isAuthenticationOrDetectionOperation(operation)
+ && operation.mClientMonitor.getToken() == token
+ && isMatchingRequestId(operation, requestId);
+ }
+
+ // By default, monitors are not associated with a request id to retain the original
+ // behavior (i.e. if no requestId is explicitly set then assume it matches)
+ private static boolean isMatchingRequestId(Operation operation, long requestId) {
+ return !operation.mClientMonitor.hasRequestId()
+ || operation.mClientMonitor.getRequestId() == requestId;
+ }
+
+ private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
+ final boolean isAuthentication =
+ operation.mClientMonitor instanceof AuthenticationConsumer;
+ final boolean isDetection =
+ operation.mClientMonitor instanceof DetectionConsumer;
return isAuthentication || isDetection;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index a15e14b..9191b8b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -31,7 +31,7 @@
/**
* A class to keep track of the enrollment state for a given client.
*/
-public abstract class EnrollClient<T> extends AcquisitionClient<T> {
+public abstract class EnrollClient<T> extends AcquisitionClient<T> implements EnrollmentModifier {
private static final String TAG = "Biometrics/EnrollClient";
@@ -40,6 +40,7 @@
protected final BiometricUtils mBiometricUtils;
private long mEnrollmentStartTimeMs;
+ private final boolean mHasEnrollmentsBeforeStarting;
/**
* @return true if the user has already enrolled the maximum number of templates.
@@ -56,6 +57,18 @@
mBiometricUtils = utils;
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
mTimeoutSec = timeoutSec;
+ mHasEnrollmentsBeforeStarting = hasEnrollments();
+ }
+
+ @Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = hasEnrollments();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
}
public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
new file mode 100644
index 0000000..c2f909b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+/**
+ * Interface for {@link BaseClientMonitor} subclasses that affect the state of enrollment.
+ */
+public interface EnrollmentModifier {
+
+ /**
+ * Callers should typically check this after
+ * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)}
+ *
+ * @return true if the user has gone from:
+ * 1) none-enrolled --> enrolled
+ * 2) enrolled --> none-enrolled
+ * but NOT any-enrolled --> more-enrolled
+ */
+ boolean hasEnrollmentStateChanged();
+
+ /**
+ * @return true if the user has any enrollments
+ */
+ boolean hasEnrollments();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 282261e..579dfd6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -40,7 +40,8 @@
* {@link #onRemoved(BiometricAuthenticator.Identifier, int)} returns true/
*/
public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Identifier, T>
- extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer {
+ extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer,
+ EnrollmentModifier {
private static final String TAG = "Biometrics/InternalCleanupClient";
@@ -61,6 +62,7 @@
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
private final List<S> mEnrolledList;
+ private final boolean mHasEnrollmentsBeforeStarting;
private BaseClientMonitor mCurrentTask;
private final Callback mEnumerateCallback = new Callback() {
@@ -115,6 +117,7 @@
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mEnrolledList = enrolledList;
+ mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
}
private void startCleanupUnknownHalTemplates() {
@@ -166,6 +169,18 @@
}
@Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = !mBiometricUtils
+ .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ }
+
+ @Override
public void onEnumerationResult(BiometricAuthenticator.Identifier identifier,
int remaining) {
if (!(mCurrentTask instanceof InternalEnumerateClient)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 383efce..2a6677e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -33,12 +33,13 @@
* A class to keep track of the remove state for a given client.
*/
public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, T>
- extends HalClientMonitor<T> implements RemovalConsumer {
+ extends HalClientMonitor<T> implements RemovalConsumer, EnrollmentModifier {
private static final String TAG = "Biometrics/RemovalClient";
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
+ private final boolean mHasEnrollmentsBeforeStarting;
public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@@ -49,6 +50,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
+ mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
}
@Override
@@ -91,6 +93,18 @@
}
@Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = !mBiometricUtils
+ .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ }
+
+ @Override
public int getProtoEnum() {
return BiometricsProto.CM_REMOVE;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 0bc4f1b..b2fd46d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -61,10 +61,11 @@
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mFaceService.prepareForAuthentication(mSensorId, requireConfirmation, token, operationId,
- userId, sensorReceiver, opPackageName, cookie, allowBackgroundAuthentication);
+ userId, sensorReceiver, opPackageName, requestId, cookie,
+ allowBackgroundAuthentication);
}
@Override
@@ -73,9 +74,9 @@
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
- mFaceService.cancelAuthenticationFromService(mSensorId, token, opPackageName);
+ mFaceService.cancelAuthenticationFromService(mSensorId, token, opPackageName, requestId);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 12d6b08..675ee545 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -250,7 +250,7 @@
}
@Override // Binder call
- public void authenticate(final IBinder token, final long operationId, int userId,
+ public long authenticate(final IBinder token, final long operationId, int userId,
final IFaceServiceReceiver receiver, final String opPackageName,
boolean isKeyguardBypassEnabled) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
@@ -270,38 +270,38 @@
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
- return;
+ return -1;
}
- provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+ return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
0 /* cookie */,
new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
statsClient, isKeyguard, isKeyguardBypassEnabled);
}
@Override // Binder call
- public void detectFace(final IBinder token, final int userId,
+ public long detectFace(final IBinder token, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "detectFace called from non-sysui package: " + opPackageName);
- return;
+ return -1;
}
if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
// If this happens, something in KeyguardUpdateMonitor is wrong. This should only
// ever be invoked when the user is encrypted or lockdown.
Slog.e(TAG, "detectFace invoked when user is not encrypted or lockdown");
- return;
+ return -1;
}
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFace");
- return;
+ return -1;
}
- provider.second.scheduleFaceDetect(provider.first, token, userId,
+ return provider.second.scheduleFaceDetect(provider.first, token, userId,
new ClientMonitorCallbackConverter(receiver), opPackageName,
BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@@ -309,8 +309,8 @@
@Override // Binder call
public void prepareForAuthentication(int sensorId, boolean requireConfirmation,
IBinder token, long operationId, int userId,
- IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie,
- boolean allowBackgroundAuthentication) {
+ IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+ int cookie, boolean allowBackgroundAuthentication) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -322,9 +322,9 @@
final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients
final boolean restricted = true; // BiometricPrompt is always restricted
provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
- new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
- BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
- isKeyguardBypassEnabled);
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
}
@Override // Binder call
@@ -341,7 +341,8 @@
}
@Override // Binder call
- public void cancelAuthentication(final IBinder token, final String opPackageName) {
+ public void cancelAuthentication(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -350,11 +351,12 @@
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
- public void cancelFaceDetect(final IBinder token, final String opPackageName) {
+ public void cancelFaceDetect(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "cancelFaceDetect called from non-sysui package: "
@@ -368,12 +370,12 @@
return;
}
- provider.second.cancelFaceDetect(provider.first, token);
+ provider.second.cancelFaceDetect(provider.first, token, requestId);
}
@Override // Binder call
public void cancelAuthenticationFromService(int sensorId, final IBinder token,
- final String opPackageName) {
+ final String opPackageName, final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -382,7 +384,7 @@
return;
}
- provider.cancelAuthentication(sensorId, token);
+ provider.cancelAuthentication(sensorId, token, requestId);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 93ab1b6..e099ba3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -101,18 +101,23 @@
void cancelEnrollment(int sensorId, @NonNull IBinder token);
- void scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
+ long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
int statsClient);
- void cancelFaceDetect(int sensorId, @NonNull IBinder token);
+ void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId);
- void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
- void cancelAuthentication(int sensorId, @NonNull IBinder token);
+ void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
+
+ void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId);
void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index d66a279..cbceba6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -65,7 +65,8 @@
@FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
FaceAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
@@ -76,6 +77,7 @@
BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
+ setRequestId(requestId);
mUsageStats = usageStats;
mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 1e73ac5..2ef0911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -43,11 +43,13 @@
@Nullable private ICancellationSignal mCancellationSignal;
public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 718b9da..4bae775 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -65,6 +65,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Provider for a single instance of the {@link IFace} HAL.
@@ -83,6 +84,8 @@
@NonNull private final UsageStats mUsageStats;
@NonNull private final ActivityTaskManager mActivityTaskManager;
@NonNull private final BiometricTaskStackListener mTaskStackListener;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@Nullable private IFace mDaemon;
@@ -110,8 +113,8 @@
&& !client.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mSensors.valueAt(i).getScheduler()
- .cancelAuthenticationOrDetection(client.getToken());
+ mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
}
@@ -356,34 +359,39 @@
}
@Override
- public void scheduleFaceDetect(int sensorId, @NonNull IBinder token,
+ public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
int userId, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
+
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceDetectClient client = new FaceDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId, opPackageName,
+ mSensors.get(sensorId).getLazySession(),
+ token, id, callback, userId, opPackageName,
sensorId, isStrongBiometric, statsClient);
scheduleForSensor(sensorId, client);
});
+
+ return id;
}
@Override
- public void cancelFaceDetect(int sensorId, @NonNull IBinder token) {
+ public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, boolean restricted, int statsClient,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
+ mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
+ userId, operationId, restricted, opPackageName, cookie,
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, isKeyguardBypassEnabled);
@@ -392,9 +400,23 @@
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
+ opPackageName, id, restricted, statsClient,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
+
+ return id;
+ }
+
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index d05333d..f4dcbbb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -87,6 +87,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
@@ -115,6 +116,8 @@
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
private final int mSensorId;
private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
@@ -605,7 +608,7 @@
}
@Override
- public void scheduleFaceDetect(int sensorId, @NonNull IBinder token,
+ public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
int userId, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, int statsClient) {
throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
@@ -613,7 +616,7 @@
}
@Override
- public void cancelFaceDetect(int sensorId, @NonNull IBinder token) {
+ public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
+ "forget to check the supportsFaceDetection flag?");
}
@@ -621,26 +624,38 @@
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull String opPackageName, boolean restricted, int statsClient,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
- mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName,
- cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric,
- statsClient, mLockoutTracker, mUsageStats, allowBackgroundAuthentication,
- isKeyguardBypassEnabled);
+ mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
+ opPackageName, cookie, false /* requireConfirmation */, mSensorId,
+ isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
mScheduler.scheduleClientMonitor(client);
});
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
- mHandler.post(() -> {
- mScheduler.cancelAuthenticationOrDetection(token);
- });
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, receiver,
+ opPackageName, id, restricted, statsClient,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
+
+ return id;
+ }
+
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 33950af..40f2801 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -57,7 +57,8 @@
private int mLastAcquire;
FaceAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
@@ -68,6 +69,7 @@
BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
+ setRequestId(requestId);
mUsageStats = usageStats;
final Resources resources = getContext().getResources();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 1e59429..52d887a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -61,10 +61,10 @@
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mFingerprintService.prepareForAuthentication(mSensorId, token, operationId, userId,
- sensorReceiver, opPackageName, cookie, allowBackgroundAuthentication);
+ sensorReceiver, opPackageName, requestId, cookie, allowBackgroundAuthentication);
}
@Override
@@ -73,9 +73,10 @@
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
- mFingerprintService.cancelAuthenticationFromService(mSensorId, token, opPackageName);
+ mFingerprintService.cancelAuthenticationFromService(
+ mSensorId, token, opPackageName, requestId);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 183fabd..f35bb7f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -35,6 +35,7 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
@@ -62,11 +63,13 @@
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Pair;
@@ -111,6 +114,7 @@
private final FingerprintServiceWrapper mServiceWrapper;
@NonNull private final List<ServiceProvider> mServiceProviders;
@NonNull private final FingerprintStateCallback mFingerprintStateCallback;
+ @NonNull private final Handler mHandler;
@GuardedBy("mLock")
@NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
@@ -125,6 +129,37 @@
*/
public void registerFingerprintStateListener(@NonNull IFingerprintStateListener listener) {
mFingerprintStateCallback.registerFingerprintStateListener(listener);
+ broadcastCurrentEnrollmentState(listener);
+ }
+
+ /**
+ * @param listener if non-null, notifies only this listener. if null, notifies all listeners
+ * in {@link FingerprintStateCallback}. This is slightly ugly, but reduces
+ * redundant code.
+ */
+ private void broadcastCurrentEnrollmentState(@Nullable IFingerprintStateListener listener) {
+ final UserManager um = UserManager.get(getContext());
+ synchronized (mLock) {
+ // Update the new listener with current state of all sensors
+ for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
+ final ServiceProvider provider = getProviderForSensor(prop.sensorId);
+ for (UserInfo userInfo : um.getAliveUsers()) {
+ final boolean enrolled = !provider
+ .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
+
+ // Defer this work and allow the loop to release the lock sooner
+ mHandler.post(() -> {
+ if (listener != null) {
+ mFingerprintStateCallback.notifyFingerprintEnrollmentStateChanged(
+ listener, userInfo.id, prop.sensorId, enrolled);
+ } else {
+ mFingerprintStateCallback.notifyAllFingerprintEnrollmentStateChanged(
+ userInfo.id, prop.sensorId, enrolled);
+ }
+ });
+ }
+ }
+ }
}
/**
@@ -143,8 +178,7 @@
return null;
}
- return provider.createTestSession(sensorId, callback, mFingerprintStateCallback,
- opPackageName);
+ return provider.createTestSession(sensorId, callback, opPackageName);
}
@Override
@@ -227,7 +261,7 @@
}
provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
- receiver, opPackageName, enrollReason, mFingerprintStateCallback);
+ receiver, opPackageName, enrollReason);
}
@Override // Binder call
@@ -245,7 +279,7 @@
@SuppressWarnings("deprecation")
@Override // Binder call
- public void authenticate(final IBinder token, final long operationId,
+ public long authenticate(final IBinder token, final long operationId,
final int sensorId, final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName) {
final int callingUid = Binder.getCallingUid();
@@ -255,7 +289,7 @@
if (!canUseFingerprint(opPackageName, true /* requireForeground */, callingUid,
callingPid, callingUserId)) {
Slog.w(TAG, "Authenticate rejecting package: " + opPackageName);
- return;
+ return -1;
}
// Keyguard check must be done on the caller's binder identity, since it also checks
@@ -270,7 +304,7 @@
// SafetyNet for b/79776455
EventLog.writeEvent(0x534e4554, "79776455");
Slog.e(TAG, "Authenticate invoked when user is encrypted or lockdown");
- return;
+ return -1;
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -290,7 +324,7 @@
}
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
- return;
+ return -1;
}
final FingerprintSensorPropertiesInternal sensorProps =
@@ -299,18 +333,17 @@
&& sensorProps != null && sensorProps.isAnyUdfpsType()) {
identity = Binder.clearCallingIdentity();
try {
- authenticateWithPrompt(operationId, sensorProps, userId, receiver);
+ return authenticateWithPrompt(operationId, sensorProps, userId, receiver);
} finally {
Binder.restoreCallingIdentity(identity);
}
- } else {
- provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
- 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
- restricted, statsClient, isKeyguard, mFingerprintStateCallback);
}
+ return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
+ restricted, statsClient, isKeyguard);
}
- private void authenticateWithPrompt(
+ private long authenticateWithPrompt(
final long operationId,
@NonNull final FingerprintSensorPropertiesInternal props,
final int userId,
@@ -387,41 +420,41 @@
}
};
- biometricPrompt.authenticateUserForOperation(
+ return biometricPrompt.authenticateUserForOperation(
new CancellationSignal(), executor, promptCallback, userId, operationId);
}
@Override
- public void detectFingerprint(final IBinder token, final int userId,
+ public long detectFingerprint(final IBinder token, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
- return;
+ return -1;
}
if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
// If this happens, something in KeyguardUpdateMonitor is wrong. This should only
// ever be invoked when the user is encrypted or lockdown.
Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
- return;
+ return -1;
}
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
- return;
+ return -1;
}
- provider.second.scheduleFingerDetect(provider.first, token, userId,
+ return provider.second.scheduleFingerDetect(provider.first, token, userId,
new ClientMonitorCallbackConverter(receiver), opPackageName,
- BiometricsProtoEnums.CLIENT_KEYGUARD, mFingerprintStateCallback);
+ BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@Override // Binder call
public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication) {
+ long requestId, int cookie, boolean allowBackgroundAuthentication) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -432,9 +465,9 @@
final boolean restricted = true; // BiometricPrompt is always restricted
provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
- new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
- BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
- mFingerprintStateCallback);
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+ allowBackgroundAuthentication);
}
@Override // Binder call
@@ -452,7 +485,8 @@
@Override // Binder call
- public void cancelAuthentication(final IBinder token, final String opPackageName) {
+ public void cancelAuthentication(final IBinder token, final String opPackageName,
+ long requestId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
@@ -469,11 +503,12 @@
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
- public void cancelFingerprintDetect(final IBinder token, final String opPackageName) {
+ public void cancelFingerprintDetect(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
@@ -489,12 +524,12 @@
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
- final String opPackageName) {
+ final String opPackageName, final long requestId) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
@@ -506,7 +541,7 @@
return;
}
- provider.cancelAuthentication(sensorId, token);
+ provider.cancelAuthentication(sensorId, token, requestId);
}
@Override // Binder call
@@ -686,27 +721,6 @@
.isEmpty();
}
- @Override // Binder call
- public boolean hasEnrolledTemplatesForAnySensor(int userId,
- @NonNull List<FingerprintSensorPropertiesInternal> sensors,
- @NonNull String opPackageName) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-
- for (FingerprintSensorPropertiesInternal prop : sensors) {
- final ServiceProvider provider = getProviderForSensor(prop.sensorId);
- if (provider == null) {
- Slog.w(TAG, "Null provider for sensorId: " + prop.sensorId
- + ", caller: " + opPackageName);
- continue;
- }
-
- if (!provider.getEnrolledFingerprints(prop.sensorId, userId).isEmpty()) {
- return true;
- }
- }
- return false;
- }
-
public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
@@ -796,10 +810,12 @@
&& Settings.Secure.getIntForUser(getContext().getContentResolver(),
Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
UserHandle.USER_CURRENT) != 0) {
- fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), hidlSensor,
+ fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
+ mFingerprintStateCallback, hidlSensor,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else {
- fingerprint21 = Fingerprint21.newInstance(getContext(), hidlSensor,
+ fingerprint21 = Fingerprint21.newInstance(getContext(),
+ mFingerprintStateCallback, hidlSensor,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
mServiceProviders.add(fingerprint21);
@@ -822,8 +838,9 @@
try {
final SensorProps[] props = fp.getSensorProps();
final FingerprintProvider provider =
- new FingerprintProvider(getContext(), props, instance,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
+ instance, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher);
mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -877,6 +894,7 @@
}
}
+ broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
broadcastAllAuthenticatorsRegistered();
});
}
@@ -974,6 +992,7 @@
mFingerprintStateCallback = new FingerprintStateCallback();
mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
mSensorProps = new ArrayList<>();
+ mHandler = new Handler(Looper.getMainLooper());
}
// Notifies the callbacks that all of the authenticators have been registered and removes the
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
index 5f998d8..0050a89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
@@ -23,6 +23,7 @@
import static android.hardware.fingerprint.FingerprintStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintStateListener;
import android.os.RemoteException;
@@ -31,6 +32,9 @@
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.EnrollmentModifier;
+import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.fingerprint.hidl.FingerprintEnrollClient;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -39,9 +43,11 @@
* A callback for receiving notifications about changes in fingerprint state.
*/
public class FingerprintStateCallback implements BaseClientMonitor.Callback {
- private @FingerprintStateListener.State int mFingerprintState;
+
@NonNull private final CopyOnWriteArrayList<IFingerprintStateListener>
- mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+ mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+
+ private @FingerprintStateListener.State int mFingerprintState;
public FingerprintStateCallback() {
mFingerprintState = STATE_IDLE;
@@ -54,8 +60,9 @@
@Override
public void onClientStarted(@NonNull BaseClientMonitor client) {
final int previousFingerprintState = mFingerprintState;
+
if (client instanceof AuthenticationClient) {
- AuthenticationClient authClient = (AuthenticationClient) client;
+ final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client;
if (authClient.isKeyguard()) {
mFingerprintState = STATE_KEYGUARD_AUTH;
} else if (authClient.isBiometricPrompt()) {
@@ -70,6 +77,7 @@
"Other authentication client: " + Utils.getClientName(client));
mFingerprintState = STATE_IDLE;
}
+
Slog.d(FingerprintService.TAG, "Fps state updated from " + previousFingerprintState
+ " to " + mFingerprintState + ", client " + client);
notifyFingerprintStateListeners(mFingerprintState);
@@ -81,6 +89,18 @@
Slog.d(FingerprintService.TAG,
"Client finished, fps state updated to " + mFingerprintState + ", client "
+ client);
+
+ if (client instanceof EnrollmentModifier) {
+ EnrollmentModifier enrollmentModifier = (EnrollmentModifier) client;
+ final boolean enrollmentStateChanged = enrollmentModifier.hasEnrollmentStateChanged();
+ Slog.d(FingerprintService.TAG, "Enrollment state changed: " + enrollmentStateChanged);
+ if (enrollmentStateChanged) {
+ notifyAllFingerprintEnrollmentStateChanged(client.getTargetUserId(),
+ client.getSensorId(),
+ enrollmentModifier.hasEnrollments());
+ }
+ }
+
notifyFingerprintStateListeners(mFingerprintState);
}
@@ -95,6 +115,32 @@
}
/**
+ * This should be invoked when:
+ * 1) Enrolled --> None-enrolled
+ * 2) None-enrolled --> enrolled
+ * 3) HAL becomes ready
+ * 4) Listener is registered
+ */
+ void notifyAllFingerprintEnrollmentStateChanged(int userId, int sensorId,
+ boolean hasEnrollments) {
+ for (IFingerprintStateListener listener : mFingerprintStateListeners) {
+ notifyFingerprintEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
+ }
+ }
+
+ /**
+ * Notifies the listener of enrollment state changes.
+ */
+ void notifyFingerprintEnrollmentStateChanged(@NonNull IFingerprintStateListener listener,
+ int userId, int sensorId, boolean hasEnrollments) {
+ try {
+ listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
+ } catch (RemoteException e) {
+ Slog.e(FingerprintService.TAG, "Remote exception", e);
+ }
+ }
+
+ /**
* Enables clients to register a FingerprintStateListener. Used by FingerprintService to forward
* updates in fingerprint sensor state to the SideFpNsEventHandler
* @param listener
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 706ac10..1772f81 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -90,25 +90,27 @@
*/
void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFingerprintServiceReceiver receiver,
- @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
void cancelEnrollment(int sensorId, @NonNull IBinder token);
- void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ int statsClient);
void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication);
+
+ long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ boolean allowBackgroundAuthentication);
void startPreparedClient(int sensorId, int cookie);
- void cancelAuthentication(int sensorId, @NonNull IBinder token);
+ void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId);
void scheduleRemove(int sensorId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
@@ -163,6 +165,5 @@
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 29f2f20..2b50b96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@
Utils.checkPermission(mContext, TEST_BIOMETRIC);
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- mFingerprintStateCallback);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 4400834..b405be7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -61,7 +61,8 @@
private boolean mIsPointerDown;
FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
int sensorId, boolean isStrongBiometric, int statsClient,
@@ -74,6 +75,7 @@
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
+ setRequestId(requestId);
mLockoutCache = lockoutCache;
mUdfpsOverlayController = udfpsOverlayController;
mSensorProps = sensorProps;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index c5dc449..da91cdd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -47,13 +47,15 @@
@Nullable private ICancellationSignal mCancellationSignal;
FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
@Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric,
int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
mUdfpsOverlayController = udfpsOverlayController;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 102b074..ca83dda 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -71,6 +71,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Provider for a single instance of the {@link IFingerprint} HAL.
@@ -81,6 +82,7 @@
private boolean mTestHalEnabled;
@NonNull private final Context mContext;
+ @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -88,6 +90,8 @@
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull private final ActivityTaskManager mActivityTaskManager;
@NonNull private final BiometricTaskStackListener mTaskStackListener;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@@ -118,8 +122,8 @@
&& !client.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mSensors.valueAt(i).getScheduler()
- .cancelAuthenticationOrDetection(client.getToken());
+ mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
}
@@ -127,10 +131,13 @@
}
}
- public FingerprintProvider(@NonNull Context context, @NonNull SensorProps[] props,
- @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ public FingerprintProvider(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
+ @NonNull SensorProps[] props, @NonNull String halInstanceName,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
mContext = context;
+ mFingerprintStateCallback = fingerprintStateCallback;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
mHandler = new Handler(Looper.getMainLooper());
@@ -332,8 +339,7 @@
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
.maxEnrollmentsPerUser;
@@ -347,13 +353,13 @@
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- fingerprintStateCallback.onClientStarted(clientMonitor);
+ mFingerprintStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- fingerprintStateCallback.onClientFinished(clientMonitor, success);
+ mFingerprintStateCallback.onClientFinished(clientMonitor, success);
if (success) {
scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
scheduleInvalidationRequest(sensorId, userId);
@@ -369,48 +375,62 @@
}
@Override
- public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- scheduleForSensor(sensorId, client, fingerprintStateCallback);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
+
+ return id;
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
+ mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
+ userId, operationId, restricted, opPackageName, cookie,
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
- scheduleForSensor(sensorId, client, fingerprintStateCallback);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
}
@Override
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
+ opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+
+ return id;
+ }
+
+ @Override
public void startPreparedClient(int sensorId, int cookie) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler().startPreparedClient(cookie));
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
@@ -444,7 +464,7 @@
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
}
@@ -459,7 +479,8 @@
mContext.getOpPackageName(), sensorId, enrolledList,
FingerprintUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client, callback);
+ scheduleForSensor(sensorId, client, new BaseClientMonitor.CompositeCallback(callback,
+ mFingerprintStateCallback));
});
}
@@ -604,9 +625,8 @@
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName) {
- return mSensors.get(sensorId).createTestSession(callback, fingerprintStateCallback);
+ return mSensors.get(sensorId).createTestSession(callback, mFingerprintStateCallback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index c00daff..79c6b1b3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@
Utils.checkPermission(mContext, TEST_BIOMETRIC);
mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- mFingerprintStateCallback);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 2f5b5c7..d2882aa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -88,6 +88,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
@@ -101,6 +102,7 @@
private boolean mTestHalEnabled;
final Context mContext;
+ @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
private final ActivityTaskManager mActivityTaskManager;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
private final BiometricScheduler mScheduler;
@@ -115,6 +117,8 @@
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
private final boolean mIsUdfps;
private final int mSensorId;
@@ -142,7 +146,8 @@
&& !client.isAlreadyDone()) {
Slog.e(TAG, "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mScheduler.cancelAuthenticationOrDetection(client.getToken());
+ mScheduler.cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
});
@@ -313,11 +318,13 @@
}
Fingerprint21(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
mContext = context;
+ mFingerprintStateCallback = fingerprintStateCallback;
mSensorProperties = sensorProps;
mSensorId = sensorProps.sensorId;
@@ -347,6 +354,7 @@
}
public static Fingerprint21 newInstance(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -358,8 +366,8 @@
final HalResultController controller = new HalResultController(sensorProps.sensorId,
context, handler,
scheduler);
- return new Fingerprint21(context, sensorProps, scheduler, handler, lockoutResetDispatcher,
- controller);
+ return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
}
@Override
@@ -553,8 +561,7 @@
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -566,13 +573,13 @@
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- fingerprintStateCallback.onClientStarted(clientMonitor);
+ mFingerprintStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- fingerprintStateCallback.onClientFinished(clientMonitor, success);
+ mFingerprintStateCallback.onClientFinished(clientMonitor, success);
if (success) {
// Update authenticatorIds
scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
@@ -591,51 +598,65 @@
}
@Override
- public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mLazyDaemon, token, listener, userId, opPackageName,
+ mLazyDaemon, token, id, listener, userId, opPackageName,
mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
+
+ return id;
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mLazyDaemon, token, listener, userId, operationId, restricted,
- opPackageName, cookie, false /* requireConfirmation */,
+ mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
+ restricted, opPackageName, cookie, false /* requireConfirmation */,
mSensorProperties.sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
allowBackgroundAuthentication, mSensorProperties);
- mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@Override
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener,
+ opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+
+ return id;
+ }
+
+ @Override
public void startPreparedClient(int sensorId, int cookie) {
mHandler.post(() -> mScheduler.startPreparedClient(cookie));
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
Slog.d(TAG, "cancelAuthentication, sensorId: " + sensorId);
- mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token));
+ mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
}
@Override
@@ -649,7 +670,7 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -666,7 +687,7 @@
0 /* fingerprintId */, userId, opPackageName,
FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -688,7 +709,8 @@
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable BaseClientMonitor.Callback callback) {
- scheduleInternalCleanup(userId, callback);
+ scheduleInternalCleanup(userId, new BaseClientMonitor.CompositeCallback(callback,
+ mFingerprintStateCallback));
}
@Override
@@ -896,9 +918,8 @@
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName) {
return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
- fingerprintStateCallback, this, mHalResultController);
+ mFingerprintStateCallback, this, mHalResultController);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 24ce867..79ad8e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -26,6 +26,7 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -42,6 +43,7 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.util.ArrayList;
@@ -270,6 +272,7 @@
}
public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -280,8 +283,8 @@
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
- return new Fingerprint21UdfpsMock(context, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
+ handler, lockoutResetDispatcher, controller);
}
private static abstract class FakeFingerRunnable implements Runnable {
@@ -400,17 +403,19 @@
// internal preemption logic is not run.
mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
operationId, user, cookie, listener, opPackageName, restricted, statsClient,
- isKeyguard, null /* fingerprintStateCallback */);
+ isKeyguard);
}
}
private Fingerprint21UdfpsMock(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull TestableBiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull MockHalResultController controller) {
- super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+ super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 5060744..7d95ec0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -59,7 +59,8 @@
private boolean mIsPointerDown;
FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
int sensorId, boolean isStrongBiometric, int statsClient,
@@ -73,6 +74,7 @@
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
+ setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
mUdfpsOverlayController = udfpsOverlayController;
mSensorProps = sensorProps;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 8e73ee6b..147a206 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -52,13 +52,15 @@
private boolean mIsPointerDown;
public FingerprintDetectClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
boolean isStrongBiometric, int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mUdfpsOverlayController = udfpsOverlayController;
mIsStrongBiometric = isStrongBiometric;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 4918185..5c0c362 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -59,7 +59,7 @@
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long sessionId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
}
@@ -68,7 +68,7 @@
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4c9d0f2..2ae5cbb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -590,7 +590,7 @@
newIndex = i - newStart;
final float newBacklightVal;
final float newNitsVal;
- isLastValue = mRawBacklight[i] > mBacklightMaximum
+ isLastValue = mRawBacklight[i] >= mBacklightMaximum
|| i >= mRawBacklight.length - 1;
// Clamp beginning and end to valid backlight values.
if (newIndex == 0) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index afd1889..c220043 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -192,7 +192,9 @@
private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
- private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
+ // This value needs to be in sync with the threshold
+ // in RefreshRateConfigs::getFrameRateDivider.
+ private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.0009f;
private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
@@ -812,7 +814,7 @@
// Override the refresh rate only if it is a divider of the current
// refresh rate. This calculation needs to be in sync with the native code
- // in RefreshRateConfigs::getRefreshRateDividerForUid
+ // in RefreshRateConfigs::getFrameRateDivider
Display.Mode currentMode = info.getMode();
float numPeriods = currentMode.getRefreshRate() / frameRateHz;
float numPeriodsRound = Math.round(numPeriods);
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 5fc301e..0a22f2f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
+import static android.os.PowerManager.BRIGHTNESS_INVALID;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,6 +41,7 @@
import android.os.IThermalService;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -60,6 +62,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.display.utils.AmbientFilter;
@@ -155,7 +158,7 @@
mAppRequestObserver = new AppRequestObserver();
mSettingsObserver = new SettingsObserver(context, handler);
mDisplayObserver = new DisplayObserver(context, handler);
- mBrightnessObserver = new BrightnessObserver(context, handler);
+ mBrightnessObserver = new BrightnessObserver(context, handler, injector);
mUdfpsObserver = new UdfpsObserver();
final BallotBox ballotBox = (displayId, priority, vote) -> {
synchronized (mLock) {
@@ -1427,8 +1430,6 @@
@Override
public void onDisplayChanged(int displayId) {
updateDisplayModes(displayId);
- // TODO: Break the coupling between DisplayObserver and BrightnessObserver.
- mBrightnessObserver.onDisplayChanged(displayId);
}
private void updateDisplayModes(int displayId) {
@@ -1465,7 +1466,7 @@
* {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
*/
@VisibleForTesting
- public class BrightnessObserver extends ContentObserver {
+ public class BrightnessObserver implements DisplayManager.DisplayListener {
private final static int LIGHT_SENSOR_RATE_MS = 250;
private int[] mLowDisplayBrightnessThresholds;
private int[] mLowAmbientBrightnessThresholds;
@@ -1488,6 +1489,8 @@
private int mBrightness = -1;
private final Context mContext;
+ private final Injector mInjector;
+ private final Handler mHandler;
// Enable light sensor only when mShouldObserveAmbientLowChange is true or
// mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
@@ -1500,9 +1503,11 @@
private int mRefreshRateInLowZone;
private int mRefreshRateInHighZone;
- BrightnessObserver(Context context, Handler handler) {
- super(handler);
+ BrightnessObserver(Context context, Handler handler, Injector injector) {
mContext = context;
+ mHandler = handler;
+ mInjector = injector;
+
mLowDisplayBrightnessThresholds = context.getResources().getIntArray(
R.array.config_brightnessThresholdsOfPeakRefreshRate);
mLowAmbientBrightnessThresholds = context.getResources().getIntArray(
@@ -1569,8 +1574,7 @@
public void observe(SensorManager sensorManager) {
mSensorManager = sensorManager;
final ContentResolver cr = mContext.getContentResolver();
- mBrightness = Settings.System.getIntForUser(cr,
- Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
+ mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
// DeviceConfig is accessible after system ready.
int[] lowDisplayBrightnessThresholds =
@@ -1603,6 +1607,10 @@
restartObserver();
mDeviceConfigDisplaySettings.startListening();
+
+ mInjector.registerDisplayListener(this, mHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED |
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
}
public void setLoggingEnabled(boolean loggingEnabled) {
@@ -1718,28 +1726,30 @@
}
}
+ @Override
+ public void onDisplayAdded(int displayId) {}
+
+ @Override
+ public void onDisplayRemoved(int displayId) {}
+
+ @Override
public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
updateDefaultDisplayState();
- }
- }
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- synchronized (mLock) {
- final ContentResolver cr = mContext.getContentResolver();
- int brightness = Settings.System.getIntForUser(cr,
- Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
- if (brightness != mBrightness) {
- mBrightness = brightness;
- onBrightnessChangedLocked();
+ // We don't support multiple display blocking zones yet, so only handle
+ // brightness changes for the default display for now.
+ int brightness = getBrightness(displayId);
+ synchronized (mLock) {
+ if (brightness != mBrightness) {
+ mBrightness = brightness;
+ onBrightnessChangedLocked();
+ }
}
}
}
private void restartObserver() {
- final ContentResolver cr = mContext.getContentResolver();
-
if (mRefreshRateInLowZone > 0) {
mShouldObserveDisplayLowChange = hasValidThreshold(
mLowDisplayBrightnessThresholds);
@@ -1760,15 +1770,6 @@
mShouldObserveAmbientHighChange = false;
}
- if (mShouldObserveDisplayLowChange || mShouldObserveDisplayHighChange) {
- // Content Service does not check if an listener has already been registered.
- // To ensure only one listener is registered, force an unregistration first.
- mInjector.unregisterBrightnessObserver(cr, this);
- mInjector.registerBrightnessObserver(cr, this);
- } else {
- mInjector.unregisterBrightnessObserver(cr, this);
- }
-
if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
Resources resources = mContext.getResources();
String lightSensorType = resources.getString(
@@ -1968,6 +1969,15 @@
return mDefaultDisplayState == Display.STATE_ON;
}
+ private int getBrightness(int displayId) {
+ final BrightnessInfo info = mInjector.getBrightnessInfo(displayId);
+ if (info != null) {
+ return BrightnessSynchronizer.brightnessFloatToInt(info.adjustedBrightness);
+ }
+
+ return BRIGHTNESS_INVALID;
+ }
+
private final class LightSensorEventListener implements SensorEventListener {
final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
private float mLastSensorData;
@@ -2283,6 +2293,7 @@
private final BallotBox mBallotBox;
private final Handler mHandler;
private final SparseIntArray mHbmMode = new SparseIntArray();
+ private final SparseBooleanArray mHbmActive = new SparseBooleanArray();
private final Injector mInjector;
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
private int mRefreshRateInHbmSunlight;
@@ -2351,6 +2362,7 @@
public void onDisplayRemoved(int displayId) {
mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
mHbmMode.delete(displayId);
+ mHbmActive.delete(displayId);
}
@Override
@@ -2360,12 +2372,17 @@
// Display no longer there. Assume we'll get an onDisplayRemoved very soon.
return;
}
+
final int hbmMode = info.highBrightnessMode;
- if (hbmMode == mHbmMode.get(displayId)) {
+ final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF &&
+ info.adjustedBrightness > info.highBrightnessTransitionPoint;
+ if (hbmMode == mHbmMode.get(displayId) &&
+ isHbmActive == mHbmActive.get(displayId)) {
// no change, ignore.
return;
}
mHbmMode.put(displayId, hbmMode);
+ mHbmActive.put(displayId, isHbmActive);
recalculateVotesForDisplay(displayId);
}
@@ -2379,28 +2396,36 @@
}
private void recalculateVotesForDisplay(int displayId) {
- final int hbmMode = mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
Vote vote = null;
- if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
- // Device resource properties take priority over DisplayDeviceConfig
- if (mRefreshRateInHbmSunlight > 0) {
- vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight,
- mRefreshRateInHbmSunlight);
- } else {
- final List<RefreshRateLimitation> limits =
- mDisplayManagerInternal.getRefreshRateLimitations(displayId);
- for (int i = 0; limits != null && i < limits.size(); i++) {
- final RefreshRateLimitation limitation = limits.get(i);
- if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
- vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max);
- break;
+ if (mHbmActive.get(displayId, false)) {
+ final int hbmMode =
+ mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+ if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+ // Device resource properties take priority over DisplayDeviceConfig
+ if (mRefreshRateInHbmSunlight > 0) {
+ vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight,
+ mRefreshRateInHbmSunlight);
+ } else {
+ final List<RefreshRateLimitation> limits =
+ mDisplayManagerInternal.getRefreshRateLimitations(displayId);
+ for (int i = 0; limits != null && i < limits.size(); i++) {
+ final RefreshRateLimitation limitation = limits.get(i);
+ if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
+ vote = Vote.forRefreshRates(limitation.range.min,
+ limitation.range.max);
+ break;
+ }
}
}
+ } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR &&
+ mRefreshRateInHbmHdr > 0) {
+ // HBM for HDR vote isn't supported through DisplayDeviceConfig yet, so look for
+ // a vote from Device properties
+ vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
+ } else {
+ Slog.w(TAG, "Unexpected HBM mode " + hbmMode + " for display ID " + displayId);
}
- }
- if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
- && mRefreshRateInHbmHdr > 0) {
- vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
+
}
mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
}
@@ -2408,6 +2433,7 @@
void dumpLocked(PrintWriter pw) {
pw.println(" HbmObserver");
pw.println(" mHbmMode: " + mHbmMode);
+ pw.println(" mHbmActive: " + mHbmActive);
pw.println(" mRefreshRateInHbmSunlight: " + mRefreshRateInHbmSunlight);
pw.println(" mRefreshRateInHbmHdr: " + mRefreshRateInHbmHdr);
}
@@ -2630,19 +2656,11 @@
}
interface Injector {
- // TODO: brightnessfloat: change this to the float setting
- Uri DISPLAY_BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
@NonNull
DeviceConfigInterface getDeviceConfig();
- void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer);
-
- void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer);
-
void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer);
@@ -2672,19 +2690,6 @@
}
@Override
- public void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- cr.registerContentObserver(DISPLAY_BRIGHTNESS_URI, false /*notifyDescendants*/,
- observer, UserHandle.USER_SYSTEM);
- }
-
- @Override
- public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- cr.unregisterContentObserver(observer);
- }
-
- @Override
public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index abbe13a..1224902 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -125,6 +125,7 @@
private static final int MSG_IGNORE_PROXIMITY = 8;
private static final int MSG_STOP = 9;
private static final int MSG_UPDATE_BRIGHTNESS = 10;
+ private static final int MSG_UPDATE_RBC = 11;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -422,13 +423,13 @@
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
private float mTemporaryAutoBrightnessAdjustment;
- // Whether a reduce bright colors (rbc) change has been initiated by the user. We want to
- // retain the current backlight level when rbc is toggled, since rbc additionally makes the
- // screen appear dimmer using screen colors rather than backlight levels, and therefore we
- // don't actually want to compensate for this by then in/decreasing the backlight when
- // toggling this feature.
+ // Whether reduce bright colors (rbc) has been turned on, or a change in strength has been
+ // requested. We want to retain the current backlight level when rbc is toggled, since rbc
+ // additionally makes the screen appear dimmer using screen colors rather than backlight levels,
+ // and therefore we don't actually want to compensate for this by then in/decreasing the
+ // backlight when toggling this feature.
// This should be false during system start up.
- private boolean mPendingUserRbcChange;
+ private boolean mPendingRbcOnOrChanged = false;
// Animators.
private ObjectAnimator mColorFadeOnAnimator;
@@ -564,23 +565,35 @@
@Override
public void onReduceBrightColorsActivationChanged(boolean activated,
boolean userInitiated) {
- applyReduceBrightColorsSplineAdjustment(userInitiated);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, activated);
+
}
@Override
public void onReduceBrightColorsStrengthChanged(int strength) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ true, /* justActivated= */ false);
}
});
if (active) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, /* justActivated= */ false);
}
} else {
mCdsi = null;
}
}
- private void applyReduceBrightColorsSplineAdjustment(boolean userInitiated) {
+ private void applyReduceBrightColorsSplineAdjustment(
+ boolean rbcStrengthChanged, boolean justActivated) {
+ final int strengthChanged = rbcStrengthChanged ? 1 : 0;
+ final int activated = justActivated ? 1 : 0;
+ mHandler.obtainMessage(MSG_UPDATE_RBC, strengthChanged, activated).sendToTarget();
+ sendUpdatePowerState();
+ }
+
+ private void handleRbcChanged(boolean strengthChanged, boolean justActivated) {
if (mBrightnessMapper == null) {
Log.w(TAG, "No brightness mapping available to recalculate splines");
return;
@@ -591,8 +604,13 @@
adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
}
mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits);
- mPendingUserRbcChange = userInitiated;
- sendUpdatePowerState();
+
+ mPendingRbcOnOrChanged = strengthChanged || justActivated;
+
+ // Reset model if strength changed OR rbc is turned off
+ if (strengthChanged || !justActivated && mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.resetShortTermModel();
+ }
}
/**
@@ -926,7 +944,8 @@
private void reloadReduceBrightColours() {
if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, /* justActivated= */ false);
}
}
@@ -1259,10 +1278,6 @@
putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true);
}
- // We save the brightness info *after* the brightness setting has been changed so that
- // the brightness info reflects the latest value.
- saveBrightnessInfo(getScreenBrightnessSetting());
-
// Apply dimming by at least some minimum amount when user activity
// timeout is about to expire.
if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -1393,6 +1408,11 @@
hadUserBrightnessPoint);
}
+ // We save the brightness info *after* the brightness setting has been changed and
+ // adjustments made so that the brightness info reflects the latest value.
+ saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+ } else {
+ saveBrightnessInfo(getScreenBrightnessSetting());
}
// Log any changes to what is currently driving the brightness setting.
@@ -1509,18 +1529,27 @@
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
mCachedBrightnessInfo.brightness,
+ mCachedBrightnessInfo.adjustedBrightness,
mCachedBrightnessInfo.brightnessMin,
mCachedBrightnessInfo.brightnessMax,
- mCachedBrightnessInfo.hbmMode);
+ mCachedBrightnessInfo.hbmMode,
+ mCachedBrightnessInfo.highBrightnessTransitionPoint);
}
}
private void saveBrightnessInfo(float brightness) {
+ saveBrightnessInfo(brightness, brightness);
+ }
+
+ private void saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
mCachedBrightnessInfo.brightness = brightness;
+ mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness;
mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin();
mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax();
mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode();
+ mCachedBrightnessInfo.highBrightnessTransitionPoint =
+ mHbmController.getTransitionPoint();
}
}
@@ -2062,21 +2091,24 @@
return true;
}
+ // We want to return true if the user has set the screen brightness.
+ // If they have just turned RBC on (and therefore added that interaction to the curve),
+ // or changed the brightness another way, then we should return true.
private boolean updateUserSetScreenBrightness() {
- final boolean brightnessSplineChanged = mPendingUserRbcChange;
- if (mPendingUserRbcChange && !Float.isNaN(mCurrentScreenBrightnessSetting)) {
+ final boolean treatAsIfUserChanged = mPendingRbcOnOrChanged;
+ if (treatAsIfUserChanged && !Float.isNaN(mCurrentScreenBrightnessSetting)) {
mLastUserSetScreenBrightness = mCurrentScreenBrightnessSetting;
}
- mPendingUserRbcChange = false;
+ mPendingRbcOnOrChanged = false;
if ((Float.isNaN(mPendingScreenBrightnessSetting)
|| mPendingScreenBrightnessSetting < 0.0f)) {
- return brightnessSplineChanged;
+ return treatAsIfUserChanged;
}
if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- return brightnessSplineChanged;
+ return treatAsIfUserChanged;
}
setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
@@ -2195,6 +2227,18 @@
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
+ synchronized (mCachedBrightnessInfo) {
+ pw.println(" mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness);
+ pw.println(" mCachedBrightnessInfo.adjustedBrightness=" +
+ mCachedBrightnessInfo.adjustedBrightness);
+ pw.println(" mCachedBrightnessInfo.brightnessMin=" +
+ mCachedBrightnessInfo.brightnessMin);
+ pw.println(" mCachedBrightnessInfo.brightnessMax=" +
+ mCachedBrightnessInfo.brightnessMax);
+ pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode);
+ pw.println(" mCachedBrightnessInfo.highBrightnessTransitionPoint=" +
+ mCachedBrightnessInfo.highBrightnessTransitionPoint);
+ }
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2406,6 +2450,12 @@
}
handleSettingsChange(false /*userSwitch*/);
break;
+
+ case MSG_UPDATE_RBC:
+ final int strengthChanged = msg.arg1;
+ final int justActivated = msg.arg2;
+ handleRbcChanged(strengthChanged == 1, justActivated == 1);
+ break;
}
}
}
@@ -2606,8 +2656,10 @@
static class CachedBrightnessInfo {
public float brightness;
+ public float adjustedBrightness;
public float brightnessMin;
public float brightnessMax;
public int hbmMode;
+ public float highBrightnessTransitionPoint;
}
}
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 2791f6a..1e1cfeb 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -59,6 +59,9 @@
private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f;
+ @VisibleForTesting
+ static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
+
private final float mBrightnessMin;
private final float mBrightnessMax;
private final Handler mHandler;
@@ -214,6 +217,14 @@
return mHbmMode;
}
+ float getTransitionPoint() {
+ if (deviceSupportsHbm()) {
+ return mHbmData.transitionPoint;
+ } else {
+ return HBM_TRANSITION_POINT_INVALID;
+ }
+ }
+
void stop() {
registerHdrListener(null /*displayToken*/);
mSkinThermalStatusObserver.stopObserving();
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index fa33338..03e421b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -209,6 +209,12 @@
*/
private AtomicBoolean mIsPendingIntentCancelled = new AtomicBoolean(false);
+ /**
+ * True if a permissions query has been issued and is being processed. Used to prevent too many
+ * queries from being issued by a single client at once.
+ */
+ private AtomicBoolean mIsPermQueryIssued = new AtomicBoolean(false);
+
/*
* True if the application creating the client has the ACCESS_CONTEXT_HUB permission.
*/
@@ -240,11 +246,11 @@
private final IContextHubTransactionCallback mQueryPermsCallback =
new IContextHubTransactionCallback.Stub() {
@Override
- public void onTransactionComplete(int result) {
- }
+ public void onTransactionComplete(int result) {}
@Override
public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ mIsPermQueryIssued.set(false);
if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
Log.e(TAG, "Permissions query failed, but still received nanoapp state");
} else if (nanoAppStateList != null) {
@@ -656,9 +662,11 @@
* communicated with in the past.
*/
private void checkNanoappPermsAsync() {
- ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
- mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
- mTransactionManager.addTransaction(transaction);
+ if (!mIsPermQueryIssued.getAndSet(true)) {
+ ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+ mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+ mTransactionManager.addTransaction(transaction);
+ }
}
private int updateNanoAppAuthState(
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index f3dcfbb..9956da2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -16,6 +16,7 @@
package com.android.server.location.gnss;
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.location.provider.ProviderProperties.ACCURACY_FINE;
import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
@@ -43,6 +44,8 @@
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_MODE_STANDALONE;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_RECURRENCE_PERIODIC;
+import static java.lang.Math.abs;
+import static java.lang.Math.max;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.AlarmManager;
@@ -85,6 +88,7 @@
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
import android.util.TimeUtils;
@@ -103,7 +107,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -156,6 +162,10 @@
private static final long LOCATION_UPDATE_DURATION_MILLIS = 10 * 1000;
// Update duration extension multiplier for emergency REQUEST_LOCATION.
private static final int EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3;
+ // maximum length gnss batching may go for (1 day)
+ private static final int MIN_BATCH_INTERVAL_MS = (int) DateUtils.SECOND_IN_MILLIS;
+ private static final long MAX_BATCH_LENGTH_MS = DateUtils.DAY_IN_MILLIS;
+ private static final long MAX_BATCH_TIMESTAMP_DELTA_MS = 500;
// Threadsafe class to hold stats reported in the Extras Bundle
private static class LocationExtras {
@@ -245,6 +255,7 @@
private boolean mShutdown;
private boolean mStarted;
private boolean mBatchingStarted;
+ private AlarmManager.OnAlarmListener mBatchingAlarm;
private long mStartedChangedElapsedRealtime;
private int mFixInterval = 1000;
@@ -845,18 +856,15 @@
mFixInterval = Integer.MAX_VALUE;
}
- // requested batch size, or zero to disable batching
- long batchSize =
- mBatchingEnabled ? mProviderRequest.getMaxUpdateDelayMillis() / Math.max(
- mFixInterval, 1) : 0;
- if (batchSize < getBatchSize()) {
- batchSize = 0;
- }
+ int batchIntervalMs = max(mFixInterval, MIN_BATCH_INTERVAL_MS);
+ long batchLengthMs = Math.min(mProviderRequest.getMaxUpdateDelayMillis(),
+ MAX_BATCH_LENGTH_MS);
// apply request to GPS engine
- if (batchSize > 0) {
+ if (mBatchingEnabled && batchLengthMs / 2 >= batchIntervalMs) {
stopNavigating();
- startBatching();
+ mFixInterval = batchIntervalMs;
+ startBatching(batchLengthMs);
} else {
stopBatching();
@@ -875,7 +883,7 @@
if (mFixInterval >= NO_FIX_TIMEOUT) {
// set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
// and our fix interval is not short
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG,
mTimeoutListener, mHandler);
}
@@ -1066,7 +1074,7 @@
// set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
// and our fix interval is not short
if (mFixInterval >= NO_FIX_TIMEOUT) {
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG, mTimeoutListener,
mHandler);
}
@@ -1090,12 +1098,37 @@
mAlarmManager.cancel(mWakeupListener);
}
- private void startBatching() {
+ private void startBatching(long batchLengthMs) {
+ long batchSize = batchLengthMs / mFixInterval;
+
if (DEBUG) {
- Log.d(TAG, "startBatching " + mFixInterval);
+ Log.d(TAG, "startBatching " + mFixInterval + " " + batchLengthMs);
}
if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), true)) {
mBatchingStarted = true;
+
+ if (batchSize < getBatchSize()) {
+ // if the batch size is smaller than the hardware batch size, use an alarm to flush
+ // locations as appropriate
+ mBatchingAlarm = () -> {
+ boolean flush = false;
+ synchronized (mLock) {
+ if (mBatchingAlarm != null) {
+ flush = true;
+ mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + batchLengthMs, TAG,
+ mBatchingAlarm, FgThread.getHandler());
+ }
+ }
+
+ if (flush) {
+ mGnssNative.flushBatch();
+ }
+ };
+ mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + batchLengthMs, TAG,
+ mBatchingAlarm, FgThread.getHandler());
+ }
} else {
Log.e(TAG, "native_start_batch failed in startBatching()");
}
@@ -1104,6 +1137,10 @@
private void stopBatching() {
if (DEBUG) Log.d(TAG, "stopBatching");
if (mBatchingStarted) {
+ if (mBatchingAlarm != null) {
+ mAlarmManager.cancel(mBatchingAlarm);
+ mBatchingAlarm = null;
+ }
mGnssNative.stopBatch();
mBatchingStarted = false;
}
@@ -1120,7 +1157,7 @@
// stop GPS until our next fix interval arrives
stopNavigating();
long now = SystemClock.elapsedRealtime();
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
mWakeupListener, mHandler);
}
@@ -1485,16 +1522,50 @@
Log.d(TAG, "Location batch of size " + locations.length + " reported");
}
+ if (locations.length > 0) {
+ // attempt to fix up timestamps if necessary
+ if (locations.length > 1) {
+ // check any realtimes outside of expected bounds
+ boolean fixRealtime = false;
+ for (int i = locations.length - 2; i >= 0; i--) {
+ long timeDeltaMs = locations[i + 1].getTime() - locations[i].getTime();
+ long realtimeDeltaMs = locations[i + 1].getElapsedRealtimeMillis()
+ - locations[i].getElapsedRealtimeMillis();
+ if (abs(timeDeltaMs - realtimeDeltaMs) > MAX_BATCH_TIMESTAMP_DELTA_MS) {
+ fixRealtime = true;
+ break;
+ }
+ }
+
+ if (fixRealtime) {
+ // sort for monotonically increasing time before fixing realtime - realtime will
+ // thus also be monotonically increasing
+ Arrays.sort(locations,
+ Comparator.comparingLong(Location::getTime));
+
+ long expectedDeltaMs =
+ locations[locations.length - 1].getTime()
+ - locations[locations.length - 1].getElapsedRealtimeMillis();
+ for (int i = locations.length - 2; i >= 0; i--) {
+ locations[i].setElapsedRealtimeNanos(
+ MILLISECONDS.toNanos(
+ max(locations[i].getTime() - expectedDeltaMs, 0)));
+ }
+ } else {
+ // sort for monotonically increasing realttime
+ Arrays.sort(locations,
+ Comparator.comparingLong(Location::getElapsedRealtimeNanos));
+ }
+ }
+
+ reportLocation(LocationResult.wrap(locations).validate());
+ }
+
Runnable[] listeners;
synchronized (mLock) {
listeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
}
-
- if (locations.length > 0) {
- reportLocation(LocationResult.wrap(locations).validate());
- }
-
for (Runnable listener : listeners) {
listener.run();
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 2227506..4d9253e 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -444,12 +444,11 @@
capabilities = networkAttributes.mCapabilities;
Log.i(TAG, String.format(
"updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
- + ", apn: %s, availableNetworkCount: %d",
+ + ", availableNetworkCount: %d",
agpsDataConnStateAsString(),
isConnected,
network,
capabilities,
- apn,
mAvailableNetworkAttributes.size()));
if (native_is_agps_ril_supported()) {
diff --git a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
index a47c48f..2df2101 100644
--- a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
@@ -100,7 +100,7 @@
return false;
}
- return mAppOps.checkOpNoThrow(permissionLevel, identity);
+ return mAppOps.checkOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity);
}
protected abstract boolean hasPermission(String permission, CallerIdentity callerIdentity);
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index d882705..83de0b3 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -243,15 +243,24 @@
intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
}
+ // send() SHOULD only run the completion callback if it completes successfully. however,
+ // b/199464864 (which could not be fixed in the S timeframe) means that it's possible
+ // for send() to throw an exception AND run the completion callback. if this happens, we
+ // would over-release the wakelock... we take matters into our own hands to ensure that
+ // the completion callback can only be run if send() completes successfully. this means
+ // the completion callback may be run inline - but as we've never specified what thread
+ // the callback is run on, this is fine.
+ GatedCallback gatedCallback = new GatedCallback(onCompleteCallback);
+
mPendingIntent.send(
mContext,
0,
intent,
- onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run()
- : null,
+ (pI, i, rC, rD, rE) -> gatedCallback.run(),
null,
null,
options.toBundle());
+ gatedCallback.allow();
}
@Override
@@ -1078,7 +1087,7 @@
}
private void onTransportFailure(Exception e) {
- if (e instanceof RemoteException) {
+ if (e instanceof PendingIntent.CanceledException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
synchronized (mLock) {
remove();
@@ -1960,11 +1969,6 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
- if (mDelayedRegister != null) {
- mAlarmHelper.cancel(mDelayedRegister);
- mDelayedRegister = null;
- }
-
// calculate how long the new request should be delayed before sending it off to the
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
@@ -1997,8 +2001,8 @@
public void onAlarm() {
synchronized (mLock) {
if (mDelayedRegister == this) {
- setProviderRequest(newRequest);
mDelayedRegister = null;
+ setProviderRequest(newRequest);
}
}
}
@@ -2025,6 +2029,11 @@
@GuardedBy("mLock")
void setProviderRequest(ProviderRequest request) {
+ if (mDelayedRegister != null) {
+ mAlarmHelper.cancel(mDelayedRegister);
+ mDelayedRegister = null;
+ }
+
EVENT_LOG.logProviderUpdateRequest(mName, request);
mProvider.getController().setRequest(request);
@@ -2734,4 +2743,49 @@
}
}
}
+
+ private static class GatedCallback implements Runnable {
+
+ private @Nullable Runnable mCallback;
+
+ @GuardedBy("this")
+ private boolean mGate;
+ @GuardedBy("this")
+ private boolean mRun;
+
+ GatedCallback(Runnable callback) {
+ mCallback = callback;
+ }
+
+ public void allow() {
+ Runnable callback = null;
+ synchronized (this) {
+ mGate = true;
+ if (mRun && mCallback != null) {
+ callback = mCallback;
+ mCallback = null;
+ }
+ }
+
+ if (callback != null) {
+ callback.run();
+ }
+ }
+
+ @Override
+ public void run() {
+ Runnable callback = null;
+ synchronized (this) {
+ mRun = true;
+ if (mGate && mCallback != null) {
+ callback = mCallback;
+ mCallback = null;
+ }
+ }
+
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 9f02c3c..a57d7db 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -903,26 +903,8 @@
if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
synchronized (mLock) {
- boolean wasA2dpOn = mGlobalBluetoothA2dpOn;
mActiveBluetoothDevice = btDevice;
mGlobalBluetoothA2dpOn = btDevice != null;
- if (wasA2dpOn != mGlobalBluetoothA2dpOn) {
- Slog.d(TAG, "GlobalBluetoothA2dpOn is changed to '"
- + mGlobalBluetoothA2dpOn + "'");
- UserRecord userRecord = mUserRecords.get(mCurrentUserId);
- if (userRecord != null) {
- for (ClientRecord cr : userRecord.mClientRecords) {
- // mSelectedRouteId will be null for BT and phone speaker.
- if (cr.mSelectedRouteId == null) {
- try {
- cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn);
- } catch (RemoteException e) {
- // Ignore exception
- }
- }
- }
- }
- }
}
}
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index bccc52f..ddaaa1e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1536,7 +1536,7 @@
@Override
public void onNullBinding(ComponentName name) {
Slog.v(TAG, "onNullBinding() called with: name = [" + name + "]");
- mServicesBound.remove(servicesBindingTag);
+ mContext.unbindService(this);
}
};
if (!mContext.bindServiceAsUser(intent,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b54e8f9..7ba0f04 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4363,8 +4363,7 @@
final int userId = r.getSbn().getUserId();
if (userId != info.userid && userId != UserHandle.USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
- throw new SecurityException("Disallowed call from listener: "
- + info.service);
+ continue;
}
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
r.getSbn().getPackageName(), r.getSbn().getTag(),
@@ -4431,8 +4430,7 @@
final int userId = r.getSbn().getUserId();
if (userId != info.userid && userId != UserHandle.USER_ALL
&& !mUserProfiles.isCurrentProfile(userId)) {
- throw new SecurityException("Disallowed call from listener: "
- + info.service);
+ continue;
}
seen.add(r);
if (!r.isSeen()) {
@@ -5399,6 +5397,7 @@
== IMPORTANCE_NONE) {
cancelNotificationsFromListener(token, new String[]{r.getKey()});
} else {
+ r.setPendingLogUpdate(true);
needsSort = true;
}
}
@@ -7451,15 +7450,21 @@
sentAccessibilityEvent = true;
}
if (DBG) Slog.v(TAG, "Interrupting!");
+ boolean isInsistentUpdate = isInsistentUpdate(record);
if (hasValidSound) {
- if (isInCall()) {
- playInCallNotification();
+ if (isInsistentUpdate) {
+ // don't reset insistent sound, it's jarring
beep = true;
} else {
- beep = playSound(record, soundUri);
- }
- if(beep) {
- mSoundNotificationKey = key;
+ if (isInCall()) {
+ playInCallNotification();
+ beep = true;
+ } else {
+ beep = playSound(record, soundUri);
+ }
+ if (beep) {
+ mSoundNotificationKey = key;
+ }
}
}
@@ -7467,9 +7472,13 @@
mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_SILENT;
if (!isInCall() && hasValidVibrate && !ringerModeSilent) {
- buzz = playVibration(record, vibration, hasValidSound);
- if(buzz) {
- mVibrateNotificationKey = key;
+ if (isInsistentUpdate) {
+ buzz = true;
+ } else {
+ buzz = playVibration(record, vibration, hasValidSound);
+ if (buzz) {
+ mVibrateNotificationKey = key;
+ }
}
}
} else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
@@ -7573,6 +7582,19 @@
}
@GuardedBy("mNotificationLock")
+ boolean isInsistentUpdate(final NotificationRecord record) {
+ return (Objects.equals(record.getKey(), mSoundNotificationKey)
+ || Objects.equals(record.getKey(), mVibrateNotificationKey))
+ && isCurrentlyInsistent();
+ }
+
+ @GuardedBy("mNotificationLock")
+ boolean isCurrentlyInsistent() {
+ return isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
+ || isLoopingRingtoneNotification(mNotificationsByKey.get(mVibrateNotificationKey));
+ }
+
+ @GuardedBy("mNotificationLock")
boolean shouldMuteNotificationLocked(final NotificationRecord record) {
// Suppressed because it's a silent update
final Notification notification = record.getNotification();
@@ -7611,10 +7633,8 @@
return true;
}
- // A looping ringtone, such as an incoming call is playing
- if (isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
- || isLoopingRingtoneNotification(
- mNotificationsByKey.get(mVibrateNotificationKey))) {
+ // A different looping ringtone, such as an incoming call is playing
+ if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
return true;
}
@@ -8038,64 +8058,151 @@
}
}
+ static class NotificationRecordExtractorData {
+ // Class that stores any field in a NotificationRecord that can change via an extractor.
+ // Used to cache previous data used in a sort.
+ int mPosition;
+ int mVisibility;
+ boolean mShowBadge;
+ boolean mAllowBubble;
+ boolean mIsBubble;
+ NotificationChannel mChannel;
+ String mGroupKey;
+ ArrayList<String> mOverridePeople;
+ ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ Integer mUserSentiment;
+ Integer mSuppressVisually;
+ ArrayList<Notification.Action> mSystemSmartActions;
+ ArrayList<CharSequence> mSmartReplies;
+ int mImportance;
+
+ // These fields may not trigger a reranking but diffs here may be logged.
+ float mRankingScore;
+ boolean mIsConversation;
+
+ NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
+ boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
+ ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
+ Integer userSentiment, Integer suppressVisually,
+ ArrayList<Notification.Action> systemSmartActions,
+ ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
+ boolean isConversation) {
+ mPosition = position;
+ mVisibility = visibility;
+ mShowBadge = showBadge;
+ mAllowBubble = allowBubble;
+ mIsBubble = isBubble;
+ mChannel = channel;
+ mGroupKey = groupKey;
+ mOverridePeople = overridePeople;
+ mSnoozeCriteria = snoozeCriteria;
+ mUserSentiment = userSentiment;
+ mSuppressVisually = suppressVisually;
+ mSystemSmartActions = systemSmartActions;
+ mSmartReplies = smartReplies;
+ mImportance = importance;
+ mRankingScore = rankingScore;
+ mIsConversation = isConversation;
+ }
+
+ // Returns whether the provided NotificationRecord differs from the cached data in any way.
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || mVisibility != r.getPackageVisibilityOverride()
+ || mShowBadge != r.canShowBadge()
+ || mAllowBubble != r.canBubble()
+ || mIsBubble != r.getNotification().isBubbleNotification()
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance();
+ }
+
+ // Returns whether the NotificationRecord has a change from this data for which we should
+ // log an update. This method specifically targets fields that may be changed via
+ // adjustments from the assistant.
+ //
+ // Fields here are the union of things in NotificationRecordLogger.shouldLogReported
+ // and NotificationRecord.applyAdjustments.
+ //
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance()
+ || !r.rankingScoreMatches(mRankingScore)
+ || mIsConversation != r.isConversation();
+ }
+ }
+
void handleRankingSort() {
if (mRankingHelper == null) return;
synchronized (mNotificationLock) {
final int N = mNotificationList.size();
// Any field that can change via one of the extractors needs to be added here.
- ArrayList<String> orderBefore = new ArrayList<>(N);
- int[] visibilities = new int[N];
- boolean[] showBadges = new boolean[N];
- boolean[] allowBubbles = new boolean[N];
- boolean[] isBubble = new boolean[N];
- ArrayList<NotificationChannel> channelBefore = new ArrayList<>(N);
- ArrayList<String> groupKeyBefore = new ArrayList<>(N);
- ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
- ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
- ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
- ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
- ArrayList<ArrayList<Notification.Action>> systemSmartActionsBefore = new ArrayList<>(N);
- ArrayList<ArrayList<CharSequence>> smartRepliesBefore = new ArrayList<>(N);
- int[] importancesBefore = new int[N];
+ ArrayMap<String, NotificationRecordExtractorData> extractorDataBefore =
+ new ArrayMap<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
- orderBefore.add(r.getKey());
- visibilities[i] = r.getPackageVisibilityOverride();
- showBadges[i] = r.canShowBadge();
- allowBubbles[i] = r.canBubble();
- isBubble[i] = r.getNotification().isBubbleNotification();
- channelBefore.add(r.getChannel());
- groupKeyBefore.add(r.getGroupKey());
- overridePeopleBefore.add(r.getPeopleOverride());
- snoozeCriteriaBefore.add(r.getSnoozeCriteria());
- userSentimentBefore.add(r.getUserSentiment());
- suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
- systemSmartActionsBefore.add(r.getSystemGeneratedSmartActions());
- smartRepliesBefore.add(r.getSmartReplies());
- importancesBefore[i] = r.getImportance();
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ i,
+ r.getPackageVisibilityOverride(),
+ r.canShowBadge(),
+ r.canBubble(),
+ r.getNotification().isBubbleNotification(),
+ r.getChannel(),
+ r.getGroupKey(),
+ r.getPeopleOverride(),
+ r.getSnoozeCriteria(),
+ r.getUserSentiment(),
+ r.getSuppressedVisualEffects(),
+ r.getSystemGeneratedSmartActions(),
+ r.getSmartReplies(),
+ r.getImportance(),
+ r.getRankingScore(),
+ r.isConversation());
+ extractorDataBefore.put(r.getKey(), extractorData);
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
- if (!orderBefore.get(i).equals(r.getKey())
- || visibilities[i] != r.getPackageVisibilityOverride()
- || showBadges[i] != r.canShowBadge()
- || allowBubbles[i] != r.canBubble()
- || isBubble[i] != r.getNotification().isBubbleNotification()
- || !Objects.equals(channelBefore.get(i), r.getChannel())
- || !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
- || !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
- || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
- || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
- || !Objects.equals(suppressVisuallyBefore.get(i),
- r.getSuppressedVisualEffects())
- || !Objects.equals(systemSmartActionsBefore.get(i),
- r.getSystemGeneratedSmartActions())
- || !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies())
- || importancesBefore[i] != r.getImportance()) {
+ if (!extractorDataBefore.containsKey(r.getKey())) {
+ // This shouldn't happen given that we just built this with all the
+ // notifications, but check just to be safe.
+ continue;
+ }
+ if (extractorDataBefore.get(r.getKey()).hasDiffForRankingLocked(r, i)) {
mHandler.scheduleSendRankingUpdate();
- return;
+ }
+
+ // If this notification is one for which we wanted to log an update, and
+ // sufficient relevant bits are different, log update.
+ if (r.hasPendingLogUpdate()) {
+ // We need to acquire the previous data associated with this specific
+ // notification, as the one at the current index may be unrelated if
+ // notification order has changed.
+ NotificationRecordExtractorData prevData = extractorDataBefore.get(r.getKey());
+ if (prevData.hasDiffForLoggingLocked(r, i)) {
+ mNotificationRecordLogger.logNotificationAdjusted(r, i, 0,
+ getGroupInstanceId(r.getSbn().getGroupKey()));
+ }
+
+ // Remove whether there was a diff or not; we've sorted the key, so if it
+ // turns out there was nothing to log, that's fine too.
+ r.setPendingLogUpdate(false);
}
}
}
@@ -8758,10 +8865,22 @@
void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
+ if (listener == null) {
+ return;
+ }
+ String listenerName = listener.component.toShortString();
if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
return;
}
+ synchronized (mNotificationLock) {
+ final NotificationRecord r = findInCurrentAndSnoozedNotificationByKeyLocked(key);
+ if (r == null) {
+ return;
+ }
+ if (!listener.enabledAndUserMatches(r.getSbn().getNormalizedUserId())){
+ return;
+ }
+ }
if (DBG) {
Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f66cfa9..b4ca511 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -200,6 +200,10 @@
private boolean mIsAppImportanceLocked;
private ArraySet<Uri> mGrantableUris;
+ // Whether this notification record should have an update logged the next time notifications
+ // are sorted.
+ private boolean mPendingLogUpdate = false;
+
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel) {
this.sbn = sbn;
@@ -648,17 +652,23 @@
final ArrayList<String> people =
adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE);
setPeopleOverride(people);
+ EventLogTags.writeNotificationAdjusted(
+ getKey(), Adjustment.KEY_PEOPLE, people.toString());
}
if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
final ArrayList<SnoozeCriterion> snoozeCriterionList =
adjustment.getSignals().getParcelableArrayList(
Adjustment.KEY_SNOOZE_CRITERIA);
setSnoozeCriteria(snoozeCriterionList);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_SNOOZE_CRITERIA,
+ snoozeCriterionList.toString());
}
if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) {
final String groupOverrideKey =
adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
setOverrideGroupKey(groupOverrideKey);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_GROUP_KEY,
+ groupOverrideKey);
}
if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
// Only allow user sentiment update from assistant if user hasn't already
@@ -667,27 +677,42 @@
&& (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
setUserSentiment(adjustment.getSignals().getInt(
Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_USER_SENTIMENT,
+ Integer.toString(getUserSentiment()));
}
}
if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) {
setSystemGeneratedSmartActions(
signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS));
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_CONTEXTUAL_ACTIONS,
+ getSystemGeneratedSmartActions().toString());
}
if (signals.containsKey(Adjustment.KEY_TEXT_REPLIES)) {
setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES));
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_TEXT_REPLIES,
+ getSmartReplies().toString());
}
if (signals.containsKey(Adjustment.KEY_IMPORTANCE)) {
int importance = signals.getInt(Adjustment.KEY_IMPORTANCE);
importance = Math.max(IMPORTANCE_UNSPECIFIED, importance);
importance = Math.min(IMPORTANCE_HIGH, importance);
setAssistantImportance(importance);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_IMPORTANCE,
+ Integer.toString(importance));
}
if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_RANKING_SCORE,
+ Float.toString(mRankingScore));
}
if (signals.containsKey(Adjustment.KEY_NOT_CONVERSATION)) {
mIsNotConversationOverride = signals.getBoolean(
Adjustment.KEY_NOT_CONVERSATION);
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_NOT_CONVERSATION,
+ Boolean.toString(mIsNotConversationOverride));
}
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
@@ -1478,6 +1503,24 @@
return sbn;
}
+ /**
+ * Returns whether this record's ranking score is approximately equal to otherScore
+ * (the difference must be within 0.0001).
+ */
+ public boolean rankingScoreMatches(float otherScore) {
+ return Math.abs(mRankingScore - otherScore) < 0.0001;
+ }
+
+ protected void setPendingLogUpdate(boolean pendingLogUpdate) {
+ mPendingLogUpdate = pendingLogUpdate;
+ }
+
+ // If a caller of this function subsequently logs the update, they should also call
+ // setPendingLogUpdate to false to make sure other callers don't also do so.
+ protected boolean hasPendingLogUpdate() {
+ return mPendingLogUpdate;
+ }
+
@VisibleForTesting
static final class Light {
public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 0e2ff75..7d8564f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -59,6 +59,20 @@
InstanceId groupId);
/**
+ * Logs a NotificationReported atom reflecting an adjustment to a notification.
+ * Unlike maybeLogNotificationPosted, this method is guaranteed to log a notification update,
+ * so the caller must take responsibility for checking that that logging update is necessary,
+ * and that the notification is meaningfully changed.
+ * @param r The NotificationRecord. If null, no action is taken.
+ * @param position The position at which this notification is ranked.
+ * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user.
+ * @param groupId The instance Id of the group summary notification, or null.
+ */
+ void logNotificationAdjusted(@Nullable NotificationRecord r,
+ int position, int buzzBeepBlink,
+ InstanceId groupId);
+
+ /**
* Logs a notification cancel / dismiss event using UiEventReported (event ids from the
* NotificationCancelledEvents enum).
* @param r The NotificationRecord. If null, no action is taken.
@@ -96,7 +110,9 @@
@UiEvent(doc = "New notification enqueued to post")
NOTIFICATION_POSTED(162),
@UiEvent(doc = "Notification substantially updated, or alerted again.")
- NOTIFICATION_UPDATED(163);
+ NOTIFICATION_UPDATED(163),
+ @UiEvent(doc = "Notification adjusted by assistant.")
+ NOTIFICATION_ADJUSTED(908);
private final int mId;
NotificationReportedEvent(int id) {
@@ -349,7 +365,8 @@
&& Objects.equals(r.getSbn().getNotification().category,
old.getSbn().getNotification().category)
&& (r.getImportance() == old.getImportance())
- && (getLoggingImportance(r) == getLoggingImportance(old)));
+ && (getLoggingImportance(r) == getLoggingImportance(old))
+ && r.rankingScoreMatches(old.getRankingScore()));
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 1a99fb0e..1859ec4 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -16,6 +16,8 @@
package com.android.server.notification;
+import android.annotation.Nullable;
+
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
@@ -37,33 +39,49 @@
if (!p.shouldLogReported(buzzBeepBlink)) {
return;
}
+ writeNotificationReportedAtom(p, NotificationReportedEvent.fromRecordPair(p),
+ position, buzzBeepBlink, groupId);
+ }
+
+ @Override
+ public void logNotificationAdjusted(@Nullable NotificationRecord r,
+ int position, int buzzBeepBlink,
+ InstanceId groupId) {
+ NotificationRecordPair p = new NotificationRecordPair(r, null);
+ writeNotificationReportedAtom(p, NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ position, buzzBeepBlink, groupId);
+ }
+
+ private void writeNotificationReportedAtom(NotificationRecordPair p,
+ NotificationReportedEvent eventType, int position, int buzzBeepBlink,
+ InstanceId groupId) {
FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
- /* int32 event_id = 1 */ NotificationReportedEvent.fromRecordPair(p).getId(),
- /* int32 uid = 2 */ r.getUid(),
- /* string package_name = 3 */ r.getSbn().getPackageName(),
+ /* int32 event_id = 1 */ eventType.getId(),
+ /* int32 uid = 2 */ p.r.getUid(),
+ /* string package_name = 3 */ p.r.getSbn().getPackageName(),
/* int32 instance_id = 4 */ p.getInstanceId(),
/* int32 notification_id_hash = 5 */ p.getNotificationIdHash(),
/* int32 channel_id_hash = 6 */ p.getChannelIdHash(),
/* string group_id_hash = 7 */ p.getGroupIdHash(),
/* int32 group_instance_id = 8 */ (groupId == null) ? 0 : groupId.getId(),
- /* bool is_group_summary = 9 */ r.getSbn().getNotification().isGroupSummary(),
- /* string category = 10 */ r.getSbn().getNotification().category,
+ /* bool is_group_summary = 9 */ p.r.getSbn().getNotification().isGroupSummary(),
+ /* string category = 10 */ p.r.getSbn().getNotification().category,
/* int32 style = 11 */ p.getStyle(),
/* int32 num_people = 12 */ p.getNumPeople(),
/* int32 position = 13 */ position,
/* android.stats.sysui.NotificationImportance importance = 14 */
- NotificationRecordLogger.getLoggingImportance(r),
+ NotificationRecordLogger.getLoggingImportance(p.r),
/* int32 alerting = 15 */ buzzBeepBlink,
/* NotificationImportanceExplanation importance_source = 16 */
- r.getImportanceExplanationCode(),
+ p.r.getImportanceExplanationCode(),
/* android.stats.sysui.NotificationImportance importance_initial = 17 */
- r.getInitialImportance(),
+ p.r.getInitialImportance(),
/* NotificationImportanceExplanation importance_initial_source = 18 */
- r.getInitialImportanceExplanationCode(),
+ p.r.getInitialImportanceExplanationCode(),
/* android.stats.sysui.NotificationImportance importance_asst = 19 */
- r.getAssistantImportance(),
+ p.r.getAssistantImportance(),
/* int32 assistant_hash = 20 */ p.getAssistantHash(),
- /* float assistant_ranking_score = 21 */ r.getRankingScore()
+ /* float assistant_ranking_score = 21 */ p.r.getRankingScore()
);
}
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 7112ae1..628a322 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -44,6 +45,8 @@
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Slog;
@@ -392,19 +395,28 @@
+ "--context <snooze-criterion-id>) <key>");
return 1;
}
- if (null == mDirectService.getNotificationRecord(key)) {
- pw.println("error: no notification matching key: " + key);
- return 1;
- }
if (duration > 0 || criterion != null) {
+ ShellNls nls = new ShellNls();
+ nls.registerAsSystemService(mDirectService.getContext(),
+ new ComponentName(nls.getClass().getPackageName(),
+ nls.getClass().getName()),
+ ActivityManager.getCurrentUser());
+ if (!waitForBind(nls)) {
+ pw.println("error: could not bind a listener in time");
+ return 1;
+ }
if (duration > 0) {
pw.println(String.format("snoozing <%s> until time: %s", key,
new Date(System.currentTimeMillis() + duration)));
+ nls.snoozeNotification(key, duration);
} else {
pw.println(String.format("snoozing <%s> until criterion: %s", key,
criterion));
+ nls.snoozeNotification(key, criterion);
}
- mDirectService.snoozeNotificationInt(key, duration, criterion, null);
+ waitForSnooze(nls, key);
+ nls.unregisterAsSystemService();
+ waitForUnbind(nls);
} else {
pw.println("error: invalid value for --" + subflag + ": " + flagarg);
return 1;
@@ -527,14 +539,17 @@
final PendingIntent pi;
if ("broadcast".equals(intentKind)) {
pi = PendingIntent.getBroadcastAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED,
UserHandle.CURRENT);
} else if ("service".equals(intentKind)) {
pi = PendingIntent.getService(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED);
} else {
pi = PendingIntent.getActivityAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
UserHandle.CURRENT);
}
builder.setContentIntent(pi);
@@ -685,9 +700,79 @@
return 0;
}
+ private void waitForSnooze(ShellNls nls, String key) {
+ for (int i = 0; i < 20; i++) {
+ StatusBarNotification[] sbns = nls.getSnoozedNotifications();
+ for (StatusBarNotification sbn : sbns) {
+ if (sbn.getKey().equals(key)) {
+ return;
+ }
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return;
+ }
+
+ private boolean waitForBind(ShellNls nls) {
+ for (int i = 0; i < 20; i++) {
+ if (nls.isConnected) {
+ Slog.i(TAG, "Bound Shell NLS");
+ return true;
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return false;
+ }
+
+ private void waitForUnbind(ShellNls nls) {
+ for (int i = 0; i < 10; i++) {
+ if (!nls.isConnected) {
+ Slog.i(TAG, "Unbound Shell NLS");
+ return;
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
@Override
public void onHelp() {
getOutPrintWriter().println(USAGE);
}
+
+ @SuppressLint("OverrideAbstract")
+ private static class ShellNls extends NotificationListenerService {
+ private static ShellNls
+ sNotificationListenerInstance = null;
+ boolean isConnected;
+
+ @Override
+ public void onListenerConnected() {
+ super.onListenerConnected();
+ sNotificationListenerInstance = this;
+ isConnected = true;
+ }
+ @Override
+ public void onListenerDisconnected() {
+ isConnected = false;
+ }
+
+ public static ShellNls getInstance() {
+ return sNotificationListenerInstance;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 96bde3d..e8a3a81 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -330,8 +330,7 @@
}
}
- if (isShortcutOk(channel) && isDeletionOk(channel)
- && !channel.isSoundMissing()) {
+ if (isShortcutOk(channel) && isDeletionOk(channel)) {
r.channels.put(id, channel);
}
}
diff --git a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
index 947405e..b276c6f 100644
--- a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
+++ b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
@@ -19,10 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Build;
import android.os.IDeviceIdentifiersPolicyService;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.UserHandle;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.server.SystemService;
@@ -65,11 +68,31 @@
@Override
public @Nullable String getSerialForPackage(String callingPackage,
String callingFeatureId) throws RemoteException {
+ if (!checkPackageBelongsToCaller(callingPackage)) {
+ throw new IllegalArgumentException(
+ "Invalid callingPackage or callingPackage does not belong to caller's uid:"
+ + Binder.getCallingUid());
+ }
+
if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext,
callingPackage, callingFeatureId, "getSerial")) {
return Build.UNKNOWN;
}
return SystemProperties.get("ro.serialno", Build.UNKNOWN);
}
+
+ private boolean checkPackageBelongsToCaller(String callingPackage) {
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ int callingPackageUid;
+ try {
+ callingPackageUid = mContext.getPackageManager().getPackageUidAsUser(
+ callingPackage, callingUserId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ return callingPackageUid == callingUid;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f6acad0..cb72f0f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2134,6 +2134,13 @@
boolean filterAppAccess(String packageName, int callingUid, int userId);
@LiveImplementation(override = LiveImplementation.MANDATORY)
void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState);
+ @LiveImplementation(override = LiveImplementation.NOT_ALLOWED)
+ FindPreferredActivityBodyResult findPreferredActivityInternal(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered);
+ @LiveImplementation(override = LiveImplementation.NOT_ALLOWED)
+ ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean debug, int userId);
}
/**
@@ -2916,7 +2923,24 @@
}
allHomeCandidates.addAll(resolveInfos);
- final String packageName = mDefaultAppProvider.getDefaultHome(userId);
+ String packageName = mDefaultAppProvider.getDefaultHome(userId);
+ if (packageName == null) {
+ // Role changes are not and cannot be atomic because its implementation lives inside
+ // a system app, so when the home role changes, there is a window when the previous
+ // role holder is removed and the new role holder is granted the preferred activity,
+ // but hasn't become the role holder yet. However, this case may be easily hit
+ // because the preferred activity change triggers a broadcast and receivers may try
+ // to get the default home activity there. So we need to fix it for this time
+ // window, and an easy workaround is to fallback to the current preferred activity.
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+ final boolean filtered = appId >= Process.FIRST_APPLICATION_UID;
+ FindPreferredActivityBodyResult result = findPreferredActivityInternal(
+ intent, null, 0, resolveInfos, true, false, false, userId, filtered);
+ ResolveInfo preferredResolveInfo = result.mPreferredResolveInfo;
+ if (preferredResolveInfo != null && preferredResolveInfo.activityInfo != null) {
+ packageName = preferredResolveInfo.activityInfo.packageName;
+ }
+ }
if (packageName == null) {
return null;
}
@@ -4848,6 +4872,284 @@
}
} // switch
}
+
+ // The body of findPreferredActivity.
+ protected FindPreferredActivityBodyResult findPreferredActivityBody(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
+ int callingUid, boolean isDeviceProvisioned) {
+ FindPreferredActivityBodyResult result = new FindPreferredActivityBodyResult();
+
+ flags = updateFlagsForResolve(
+ flags, userId, callingUid, false /*includeInstantApps*/,
+ isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+ resolvedType, flags));
+ intent = updateIntentForResolve(intent);
+
+ // Try to find a matching persistent preferred activity.
+ result.mPreferredResolveInfo = findPersistentPreferredActivityLP(intent,
+ resolvedType, flags, query, debug, userId);
+
+ // If a persistent preferred activity matched, use it.
+ if (result.mPreferredResolveInfo != null) {
+ return result;
+ }
+
+ PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
+ // Get the list of preferred activities that handle the intent
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
+ List<PreferredActivity> prefs = pir != null
+ ? pir.queryIntent(intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+ userId)
+ : null;
+ if (prefs != null && prefs.size() > 0) {
+
+ // First figure out how good the original match set is.
+ // We will only allow preferred activities that came
+ // from the same match quality.
+ int match = 0;
+
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
+
+ final int N = query.size();
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Match for " + ri.activityInfo
+ + ": 0x" + Integer.toHexString(match));
+ }
+ if (ri.match > match) {
+ match = ri.match;
+ }
+ }
+
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Best match: 0x" + Integer.toHexString(match));
+ }
+ match &= IntentFilter.MATCH_CATEGORY_MASK;
+ final int M = prefs.size();
+ for (int i = 0; i < M; i++) {
+ final PreferredActivity pa = prefs.get(i);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Checking PreferredActivity ds="
+ + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
+ + "\n component=" + pa.mPref.mComponent);
+ pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ }
+ if (pa.mPref.mMatch != match) {
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Skipping bad match "
+ + Integer.toHexString(pa.mPref.mMatch));
+ }
+ continue;
+ }
+ // If it's not an "always" type preferred activity and that's what we're
+ // looking for, skip it.
+ if (always && !pa.mPref.mAlways) {
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
+ continue;
+ }
+ final ActivityInfo ai = getActivityInfo(
+ pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
+ | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ userId);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Found preferred activity:");
+ if (ai != null) {
+ ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ } else {
+ Slog.v(TAG, " null");
+ }
+ }
+ final boolean excludeSetupWizardHomeActivity = isHomeIntent(intent)
+ && !isDeviceProvisioned;
+ final boolean allowSetMutation = !excludeSetupWizardHomeActivity
+ && !queryMayBeFiltered;
+ if (ai == null) {
+ // Do not remove launcher's preferred activity during SetupWizard
+ // due to it may not install yet
+ if (!allowSetMutation) {
+ continue;
+ }
+
+ // This previously registered preferred activity
+ // component is no longer known. Most likely an update
+ // to the app was installed and in the new version this
+ // component no longer exists. Clean it up by removing
+ // it from the preferred activities list, and skip it.
+ Slog.w(TAG, "Removing dangling preferred activity: "
+ + pa.mPref.mComponent);
+ pir.removeFilter(pa);
+ result.mChanged = true;
+ continue;
+ }
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (!ri.activityInfo.applicationInfo.packageName
+ .equals(ai.applicationInfo.packageName)) {
+ continue;
+ }
+ if (!ri.activityInfo.name.equals(ai.name)) {
+ continue;
+ }
+
+ if (removeMatches && allowSetMutation) {
+ pir.removeFilter(pa);
+ result.mChanged = true;
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
+ }
+ break;
+ }
+
+ // Okay we found a previously set preferred or last chosen app.
+ // If the result set is different from when this
+ // was created, and is not a subset of the preferred set, we need to
+ // clear it and re-ask the user their preference, if we're looking for
+ // an "always" type entry.
+
+ if (always && !pa.mPref.sameSet(query, excludeSetupWizardHomeActivity)) {
+ if (pa.mPref.isSuperset(query, excludeSetupWizardHomeActivity)) {
+ if (allowSetMutation) {
+ // some components of the set are no longer present in
+ // the query, but the preferred activity can still be reused
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Result set changed, but PreferredActivity"
+ + " is still valid as only non-preferred"
+ + " components were removed for " + intent
+ + " type " + resolvedType);
+ }
+ // remove obsolete components and re-add the up-to-date
+ // filter
+ PreferredActivity freshPa = new PreferredActivity(pa,
+ pa.mPref.mMatch,
+ pa.mPref.discardObsoleteComponents(query),
+ pa.mPref.mComponent,
+ pa.mPref.mAlways);
+ pir.removeFilter(pa);
+ pir.addFilter(freshPa);
+ result.mChanged = true;
+ } else {
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Do not remove preferred activity");
+ }
+ }
+ } else {
+ if (allowSetMutation) {
+ Slog.i(TAG,
+ "Result set changed, dropping preferred activity "
+ + "for " + intent + " type "
+ + resolvedType);
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG,
+ "Removing preferred activity since set changed "
+ + pa.mPref.mComponent);
+ }
+ pir.removeFilter(pa);
+ // Re-add the filter as a "last chosen" entry (!always)
+ PreferredActivity lastChosen = new PreferredActivity(
+ pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
+ false);
+ pir.addFilter(lastChosen);
+ result.mChanged = true;
+ }
+ result.mPreferredResolveInfo = null;
+ return result;
+ }
+ }
+
+ // Yay! Either the set matched or we're looking for the last chosen
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Returning preferred activity: "
+ + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ }
+ result.mPreferredResolveInfo = ri;
+ return result;
+ }
+ }
+ }
+ return result;
+ }
+
+ public final FindPreferredActivityBodyResult findPreferredActivityInternal(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+
+ final int callingUid = Binder.getCallingUid();
+ // Do NOT hold the packages lock; this calls up into the settings provider which
+ // could cause a deadlock.
+ final boolean isDeviceProvisioned =
+ android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
+ // Find the preferred activity - the lock is held inside the method.
+ return findPreferredActivityBody(
+ intent, resolvedType, flags, query, always, removeMatches, debug,
+ userId, queryMayBeFiltered, callingUid, isDeviceProvisioned);
+ }
+
+ public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType,
+ int flags, List<ResolveInfo> query, boolean debug, int userId) {
+ final int N = query.size();
+ PersistentPreferredIntentResolver ppir =
+ mSettings.getPersistentPreferredActivities(userId);
+ // Get the list of persistent preferred activities that handle the intent
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Looking for persistent preferred activities...");
+ }
+ List<PersistentPreferredActivity> pprefs = ppir != null
+ ? ppir.queryIntent(intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+ userId)
+ : null;
+ if (pprefs != null && pprefs.size() > 0) {
+ final int M = pprefs.size();
+ for (int i = 0; i < M; i++) {
+ final PersistentPreferredActivity ppa = pprefs.get(i);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Checking PersistentPreferredActivity ds="
+ + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
+ + "\n component=" + ppa.mComponent);
+ ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ }
+ final ActivityInfo ai = getActivityInfo(ppa.mComponent,
+ flags | MATCH_DISABLED_COMPONENTS, userId);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Found persistent preferred activity:");
+ if (ai != null) {
+ ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ } else {
+ Slog.v(TAG, " null");
+ }
+ }
+ if (ai == null) {
+ // This previously registered persistent preferred activity
+ // component is no longer known. Ignore it and do NOT remove it.
+ continue;
+ }
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (!ri.activityInfo.applicationInfo.packageName
+ .equals(ai.applicationInfo.packageName)) {
+ continue;
+ }
+ if (!ri.activityInfo.name.equals(ai.name)) {
+ continue;
+ }
+ // Found a persistent preference that can handle the intent.
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Returning persistent preferred activity: "
+ + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ }
+ return ri;
+ }
+ }
+ }
+ return null;
+ }
}
/**
@@ -5007,6 +5309,16 @@
super.dump(type, fd, pw, dumpState);
}
}
+ public final FindPreferredActivityBodyResult findPreferredActivityBody(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
+ int callingUid, boolean isDeviceProvisioned) {
+ synchronized (mLock) {
+ return super.findPreferredActivityBody(intent, resolvedType, flags, query, always,
+ removeMatches, debug, userId, queryMayBeFiltered, callingUid,
+ isDeviceProvisioned);
+ }
+ }
}
/**
@@ -5574,6 +5886,28 @@
current.release();
}
}
+ public final FindPreferredActivityBodyResult findPreferredActivityInternal(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+ ThreadComputer current = live();
+ try {
+ return current.mComputer.findPreferredActivityInternal(intent, resolvedType, flags,
+ query, always, removeMatches, debug, userId, queryMayBeFiltered);
+ } finally {
+ current.release();
+ }
+ }
+ public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean debug,
+ int userId) {
+ ThreadComputer current = live();
+ try {
+ return current.mComputer.findPersistentPreferredActivityLP(intent, resolvedType,
+ flags, query, debug, userId);
+ } finally {
+ current.release();
+ }
+ }
}
@@ -7222,6 +7556,7 @@
t.traceEnd();
mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions);
+ mPermissionManager.readLegacyPermissionStateTEMP();
if (!mOnlyCore && mFirstBoot) {
requestCopyPreoptedFiles(mInjector);
@@ -7637,7 +7972,6 @@
+ ((SystemClock.uptimeMillis()-startTime)/1000f)
+ " seconds");
- mPermissionManager.readLegacyPermissionStateTEMP();
// If the build fingerprint has changed since the last time we booted,
// we need to re-grant app permission to catch any new ones that
// appear. This is really a hack, and means that apps can in some
@@ -9070,7 +9404,7 @@
/**
* Update given intent when being used to request {@link ResolveInfo}.
*/
- private Intent updateIntentForResolve(Intent intent) {
+ private static Intent updateIntentForResolve(Intent intent) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
}
@@ -10242,7 +10576,7 @@
userId);
// Find any earlier preferred or last chosen entries and nuke them
findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, 0, false, true, false, userId);
+ intent, resolvedType, flags, query, false, true, false, userId);
// Add the new activity as the last chosen for this filter
addPreferredActivity(filter, match, null, activity, false, userId,
"Setting last chosen", false);
@@ -10258,7 +10592,7 @@
final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
userId);
return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, 0, false, false, false, userId);
+ intent, resolvedType, flags, query, false, false, false, userId);
}
private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
@@ -10300,7 +10634,7 @@
// If we have saved a preference for a preferred activity for
// this Intent, use that.
ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
- flags, query, r0.priority, true, false, debug, userId, queryMayBeFiltered);
+ flags, query, true, false, debug, userId, queryMayBeFiltered);
if (ri != null) {
return ri;
}
@@ -10413,287 +10747,72 @@
}
@GuardedBy("mLock")
- private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType,
+ private ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType,
int flags, List<ResolveInfo> query, boolean debug, int userId) {
- final int N = query.size();
- PersistentPreferredIntentResolver ppir = mSettings.getPersistentPreferredActivities(userId);
- // Get the list of persistent preferred activities that handle the intent
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for presistent preferred activities...");
- List<PersistentPreferredActivity> pprefs = ppir != null
- ? ppir.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId)
- : null;
- if (pprefs != null && pprefs.size() > 0) {
- final int M = pprefs.size();
- for (int i=0; i<M; i++) {
- final PersistentPreferredActivity ppa = pprefs.get(i);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Checking PersistentPreferredActivity ds="
- + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
- + "\n component=" + ppa.mComponent);
- ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- }
- final ActivityInfo ai = getActivityInfo(ppa.mComponent,
- flags | MATCH_DISABLED_COMPONENTS, userId);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Found persistent preferred activity:");
- if (ai != null) {
- ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- } else {
- Slog.v(TAG, " null");
- }
- }
- if (ai == null) {
- // This previously registered persistent preferred activity
- // component is no longer known. Ignore it and do NOT remove it.
- continue;
- }
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (!ri.activityInfo.applicationInfo.packageName
- .equals(ai.applicationInfo.packageName)) {
- continue;
- }
- if (!ri.activityInfo.name.equals(ai.name)) {
- continue;
- }
- // Found a persistent preference that can handle the intent.
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Returning persistent preferred activity: " +
- ri.activityInfo.packageName + "/" + ri.activityInfo.name);
- }
- return ri;
- }
- }
- }
- return null;
+ return mComputer.findPersistentPreferredActivityLP(intent,
+ resolvedType,
+ flags, query, debug, userId);
}
- private boolean isHomeIntent(Intent intent) {
+ private static boolean isHomeIntent(Intent intent) {
return ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(CATEGORY_HOME)
&& intent.hasCategory(CATEGORY_DEFAULT);
}
+
+ // findPreferredActivityBody returns two items: a "things changed" flag and a
+ // ResolveInfo, which is the preferred activity itself.
+ private static class FindPreferredActivityBodyResult {
+ boolean mChanged;
+ ResolveInfo mPreferredResolveInfo;
+ }
+
+ private FindPreferredActivityBodyResult findPreferredActivityInternal(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+ return mComputer.findPreferredActivityInternal(
+ intent, resolvedType, flags,
+ query, always,
+ removeMatches, debug, userId, queryMayBeFiltered);
+ }
+
ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
- List<ResolveInfo> query, int priority, boolean always,
+ List<ResolveInfo> query, boolean always,
boolean removeMatches, boolean debug, int userId) {
return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, priority, always, removeMatches, debug, userId,
+ intent, resolvedType, flags, query, always, removeMatches, debug, userId,
UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
}
// TODO: handle preferred activities missing while user has amnesia
/** <b>must not hold {@link #mLock}</b> */
ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
- List<ResolveInfo> query, int priority, boolean always,
+ List<ResolveInfo> query, boolean always,
boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
if (Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ " is holding mLock", new Throwable());
}
if (!mUserManager.exists(userId)) return null;
- final int callingUid = Binder.getCallingUid();
- // Do NOT hold the packages lock; this calls up into the settings provider which
- // could cause a deadlock.
- final boolean isDeviceProvisioned =
- android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
- flags = updateFlagsForResolve(
- flags, userId, callingUid, false /*includeInstantApps*/,
- isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
- flags));
- intent = updateIntentForResolve(intent);
- // writer
- synchronized (mLock) {
- // Try to find a matching persistent preferred activity.
- ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
- debug, userId);
- // If a persistent preferred activity matched, use it.
- if (pri != null) {
- return pri;
+ FindPreferredActivityBodyResult body = findPreferredActivityInternal(
+ intent, resolvedType, flags, query, always,
+ removeMatches, debug, userId, queryMayBeFiltered);
+ if (body.mChanged) {
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
}
-
- PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
- // Get the list of preferred activities that handle the intent
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
- List<PreferredActivity> prefs = pir != null
- ? pir.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId)
- : null;
- if (prefs != null && prefs.size() > 0) {
- boolean changed = false;
- try {
- // First figure out how good the original match set is.
- // We will only allow preferred activities that came
- // from the same match quality.
- int match = 0;
-
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
-
- final int N = query.size();
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Match for " + ri.activityInfo
- + ": 0x" + Integer.toHexString(match));
- if (ri.match > match) {
- match = ri.match;
- }
- }
-
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Best match: 0x"
- + Integer.toHexString(match));
-
- match &= IntentFilter.MATCH_CATEGORY_MASK;
- final int M = prefs.size();
- for (int i=0; i<M; i++) {
- final PreferredActivity pa = prefs.get(i);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Checking PreferredActivity ds="
- + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
- + "\n component=" + pa.mPref.mComponent);
- pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- }
- if (pa.mPref.mMatch != match) {
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping bad match "
- + Integer.toHexString(pa.mPref.mMatch));
- continue;
- }
- // If it's not an "always" type preferred activity and that's what we're
- // looking for, skip it.
- if (always && !pa.mPref.mAlways) {
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
- continue;
- }
- final ActivityInfo ai = getActivityInfo(
- pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
- | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- userId);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Found preferred activity:");
- if (ai != null) {
- ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- } else {
- Slog.v(TAG, " null");
- }
- }
- final boolean excludeSetupWizardHomeActivity = isHomeIntent(intent)
- && !isDeviceProvisioned;
- final boolean allowSetMutation = !excludeSetupWizardHomeActivity
- && !queryMayBeFiltered;
- if (ai == null) {
- // Do not remove launcher's preferred activity during SetupWizard
- // due to it may not install yet
- if (!allowSetMutation) {
- continue;
- }
-
- // This previously registered preferred activity
- // component is no longer known. Most likely an update
- // to the app was installed and in the new version this
- // component no longer exists. Clean it up by removing
- // it from the preferred activities list, and skip it.
- Slog.w(TAG, "Removing dangling preferred activity: "
- + pa.mPref.mComponent);
- pir.removeFilter(pa);
- changed = true;
- continue;
- }
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (!ri.activityInfo.applicationInfo.packageName
- .equals(ai.applicationInfo.packageName)) {
- continue;
- }
- if (!ri.activityInfo.name.equals(ai.name)) {
- continue;
- }
-
- if (removeMatches && allowSetMutation) {
- pir.removeFilter(pa);
- changed = true;
- if (DEBUG_PREFERRED) {
- Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
- }
- break;
- }
-
- // Okay we found a previously set preferred or last chosen app.
- // If the result set is different from when this
- // was created, and is not a subset of the preferred set, we need to
- // clear it and re-ask the user their preference, if we're looking for
- // an "always" type entry.
-
- if (always && !pa.mPref.sameSet(query, excludeSetupWizardHomeActivity)) {
- if (pa.mPref.isSuperset(query, excludeSetupWizardHomeActivity)) {
- if (allowSetMutation) {
- // some components of the set are no longer present in
- // the query, but the preferred activity can still be reused
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Result set changed, but PreferredActivity"
- + " is still valid as only non-preferred"
- + " components were removed for " + intent
- + " type " + resolvedType);
- }
- // remove obsolete components and re-add the up-to-date
- // filter
- PreferredActivity freshPa = new PreferredActivity(pa,
- pa.mPref.mMatch,
- pa.mPref.discardObsoleteComponents(query),
- pa.mPref.mComponent,
- pa.mPref.mAlways);
- pir.removeFilter(pa);
- pir.addFilter(freshPa);
- changed = true;
- } else {
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Do not remove preferred activity");
- }
- }
- } else {
- if (allowSetMutation) {
- Slog.i(TAG,
- "Result set changed, dropping preferred activity "
- + "for " + intent + " type "
- + resolvedType);
- if (DEBUG_PREFERRED) {
- Slog.v(TAG,
- "Removing preferred activity since set changed "
- + pa.mPref.mComponent);
- }
- pir.removeFilter(pa);
- // Re-add the filter as a "last chosen" entry (!always)
- PreferredActivity lastChosen = new PreferredActivity(
- pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
- false);
- pir.addFilter(lastChosen);
- changed = true;
- }
- return null;
- }
- }
-
- // Yay! Either the set matched or we're looking for the last chosen
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Returning preferred activity: "
- + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
- return ri;
- }
- }
- } finally {
- if (changed) {
- if (DEBUG_PREFERRED) {
- Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
- }
- scheduleWritePackageRestrictionsLocked(userId);
- }
- }
+ synchronized (mLock) {
+ scheduleWritePackageRestrictionsLocked(userId);
}
}
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "No preferred activity to return");
- return null;
+ if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
+ Slog.v(TAG, "No preferred activity to return");
+ }
+ return body.mPreferredResolveInfo;
}
/*
@@ -16046,8 +16165,7 @@
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
- void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
- boolean suspended) {
+ void sendPackagesSuspendedForUser(String intent, String[] pkgList, int[] uidList, int userId) {
final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
@@ -16088,11 +16206,8 @@
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
? null : allowListsToSend.get(i);
- sendPackageBroadcast(
- suspended ? Intent.ACTION_PACKAGES_SUSPENDED
- : Intent.ACTION_PACKAGES_UNSUSPENDED,
- null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
- userIds, null, allowList, null);
+ sendPackageBroadcast(intent, null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null,
+ null, userIds, null, allowList, null);
}
}
@@ -16406,6 +16521,8 @@
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
+ final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
+ final IntArray modifiedUids = new IntArray(packageNames.length);
final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
final boolean[] canSuspend = suspended ? canSuspendPackageForUserInternal(packageNames,
userId) : null;
@@ -16433,13 +16550,14 @@
unactionedPackages.add(packageName);
continue;
}
- boolean packageUnsuspended;
+ final boolean packageUnsuspended;
+ final boolean packageModified;
synchronized (mLock) {
if (suspended) {
- pkgSetting.addOrUpdateSuspension(callingPackage, dialogInfo, appExtras,
- launcherExtras, userId);
+ packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
+ dialogInfo, appExtras, launcherExtras, userId);
} else {
- pkgSetting.removeSuspension(callingPackage, userId);
+ packageModified = pkgSetting.removeSuspension(callingPackage, userId);
}
packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
}
@@ -16447,18 +16565,29 @@
changedPackagesList.add(packageName);
changedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
}
+ if (packageModified) {
+ modifiedPackagesList.add(packageName);
+ modifiedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
+ }
}
if (!changedPackagesList.isEmpty()) {
- final String[] changedPackages = changedPackagesList.toArray(
- new String[changedPackagesList.size()]);
- sendPackagesSuspendedForUser(changedPackages, changedUids.toArray(), userId, suspended);
+ final String[] changedPackages = changedPackagesList.toArray(new String[0]);
+ sendPackagesSuspendedForUser(
+ suspended ? Intent.ACTION_PACKAGES_SUSPENDED
+ : Intent.ACTION_PACKAGES_UNSUSPENDED,
+ changedPackages, changedUids.toArray(), userId);
sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
}
}
- return unactionedPackages.toArray(new String[unactionedPackages.size()]);
+ // Send the suspension changed broadcast to ensure suspension state is not stale.
+ if (!modifiedPackagesList.isEmpty()) {
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
+ }
+ return unactionedPackages.toArray(new String[0]);
}
@Override
@@ -16587,7 +16716,8 @@
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
- sendPackagesSuspendedForUser(packageArray, unsuspendedUids.toArray(), userId, false);
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
+ packageArray, unsuspendedUids.toArray(), userId);
}
}
@@ -22529,8 +22659,9 @@
removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), userId, appId);
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+ StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(userId)) {
+ if (StorageManager.isUserKeyUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(userId)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -23470,7 +23601,7 @@
final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
- intent, null, 0, resolveInfos, 0, true, false, false, userId);
+ intent, null, 0, resolveInfos, true, false, false, userId);
final String packageName = preferredResolveInfo != null
&& preferredResolveInfo.activityInfo != null
? preferredResolveInfo.activityInfo.packageName : null;
@@ -25395,9 +25526,11 @@
// Reconcile app data for all started/unlocked users
final StorageManager sm = mInjector.getSystemService(StorageManager.class);
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+ StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
for (UserInfo user : mUserManager.getUsers(false /* includeDying */)) {
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)
+ && smInternal.isCeStoragePrepared(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -25737,7 +25870,8 @@
StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)
+ && smInternal.isCeStoragePrepared(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE;
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 88dd033..5536fc5 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -455,7 +455,7 @@
return state.suspendParams != null && state.suspendParams.containsKey(suspendingPackage);
}
- void addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
+ boolean addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
final PackageUserState existingUserState = modifyUserState(userId);
final PackageUserState.SuspendParams newSuspendParams =
@@ -464,21 +464,27 @@
if (existingUserState.suspendParams == null) {
existingUserState.suspendParams = new ArrayMap<>();
}
- existingUserState.suspendParams.put(suspendingPackage, newSuspendParams);
+ final PackageUserState.SuspendParams oldSuspendParams =
+ existingUserState.suspendParams.put(suspendingPackage, newSuspendParams);
existingUserState.suspended = true;
onChanged();
+ return !Objects.equals(oldSuspendParams, newSuspendParams);
}
- void removeSuspension(String suspendingPackage, int userId) {
+ boolean removeSuspension(String suspendingPackage, int userId) {
+ boolean wasModified = false;
final PackageUserState existingUserState = modifyUserState(userId);
if (existingUserState.suspendParams != null) {
- existingUserState.suspendParams.remove(suspendingPackage);
+ if (existingUserState.suspendParams.remove(suspendingPackage) != null) {
+ wasModified = true;
+ }
if (existingUserState.suspendParams.size() == 0) {
existingUserState.suspendParams = null;
}
}
existingUserState.suspended = (existingUserState.suspendParams != null);
onChanged();
+ return wasModified;
}
void removeSuspension(Predicate<String> suspendingPackagePredicate, int userId) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d4feb3a..6d8137e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -80,6 +80,7 @@
import android.os.UserManager.EnforcingUser;
import android.os.UserManager.QuietModeFlag;
import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
import android.provider.Settings;
import android.security.GateKeeper;
import android.service.gatekeeper.IGateKeeperService;
@@ -4815,6 +4816,10 @@
// Migrate only if build fingerprints mismatch
boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
+
+ StorageManagerInternal smInternal = LocalServices.getService(StorageManagerInternal.class);
+ smInternal.markCeStoragePrepared(userId);
+
mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE, migrateAppsData);
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index dab980a..3019146 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -557,11 +557,14 @@
grantPermissionsToSystemPackage(pm, verifier, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS);
// SetupWizard
- grantPermissionsToSystemPackage(pm,
- ArrayUtils.firstOrNull(getKnownPackages(
- PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId)), userId,
- PHONE_PERMISSIONS, CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
- CAMERA_PERMISSIONS);
+ final String setupWizardPackage = ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId));
+ grantPermissionsToSystemPackage(pm, setupWizardPackage, userId, PHONE_PERMISSIONS,
+ CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, CAMERA_PERMISSIONS);
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) {
+ grantPermissionsToSystemPackage(
+ pm, setupWizardPackage, userId, NEARBY_DEVICES_PERMISSIONS);
+ }
// Camera
grantPermissionsToSystemPackage(pm,
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
index b1676d0..ea554d3 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
@@ -30,6 +30,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.ILegacyPermissionManager;
+import android.util.EventLog;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -187,10 +188,25 @@
private void verifyCallerCanCheckAccess(String packageName, String message, int pid, int uid) {
// If the check is being requested by an app then only allow the app to query its own
// access status.
+ boolean reportError = false;
int callingUid = mInjector.getCallingUid();
int callingPid = mInjector.getCallingPid();
if (UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID && (callingUid != uid
|| callingPid != pid)) {
+ reportError = true;
+ }
+ // If the query is against an app on the device, then the check should only be allowed if
+ // the provided uid matches that of the specified package.
+ if (packageName != null && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+ int packageUid = mInjector.getPackageUidForUser(packageName, UserHandle.getUserId(uid));
+ if (uid != packageUid) {
+ EventLog.writeEvent(0x534e4554, "193441322",
+ UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID
+ ? callingUid : uid, "Package uid mismatch");
+ reportError = true;
+ }
+ }
+ if (reportError) {
String response = String.format(
"Calling uid %d, pid %d cannot access for package %s (uid=%d, pid=%d): %s",
callingUid, callingPid, packageName, uid, pid, message);
@@ -385,12 +401,14 @@
@VisibleForTesting
public static class Injector {
private final Context mContext;
+ private final PackageManagerInternal mPackageManagerInternal;
/**
* Public constructor that accepts a {@code context} within which to operate.
*/
public Injector(@NonNull Context context) {
mContext = context;
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
/**
@@ -453,5 +471,12 @@
return mContext.getPackageManager().getApplicationInfoAsUser(packageName, 0,
UserHandle.getUserHandleForUid(uid));
}
+
+ /**
+ * Returns the uid for the specified {@code packageName} under the provided {@code userId}.
+ */
+ public int getPackageUidForUser(String packageName, int userId) {
+ return mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e408822..b6ca67d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -484,6 +484,7 @@
int mLidNavigationAccessibility;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
+ long mLongPressOnPowerAssistantTimeoutMs;
int mVeryLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
@@ -732,6 +733,9 @@
Settings.Global.POWER_BUTTON_LONG_PRESS), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.POWER_BUTTON_VERY_LONG_PRESS), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
@@ -1732,6 +1736,8 @@
com.android.internal.R.integer.config_shortPressOnPowerBehavior);
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
+ mLongPressOnPowerAssistantTimeoutMs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnPowerDurationMs);
mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
@@ -1955,7 +1961,7 @@
*/
private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
PowerKeyRule(int gestures) {
- super(KEYCODE_POWER, gestures);
+ super(mContext, KEYCODE_POWER, gestures);
}
@Override
@@ -1970,6 +1976,15 @@
}
@Override
+ long getLongPressTimeoutMs() {
+ if (getResolvedLongPressOnPowerBehavior() == LONG_PRESS_POWER_ASSISTANT) {
+ return mLongPressOnPowerAssistantTimeoutMs;
+ } else {
+ return super.getLongPressTimeoutMs();
+ }
+ }
+
+ @Override
void onLongPress(long eventTime) {
if (mSingleKeyGestureDetector.beganFromNonInteractive()
&& !mSupportLongPressPowerWhenNonInteractive) {
@@ -1997,7 +2012,7 @@
*/
private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
BackKeyRule(int gestures) {
- super(KEYCODE_BACK, gestures);
+ super(mContext, KEYCODE_BACK, gestures);
}
@Override
@@ -2017,7 +2032,7 @@
}
private void initSingleKeyGestureRules() {
- mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
+ mSingleKeyGestureDetector = new SingleKeyGestureDetector();
int powerKeyGestures = 0;
if (hasVeryLongPressOnPowerBehavior()) {
@@ -2115,6 +2130,11 @@
Settings.Global.POWER_BUTTON_LONG_PRESS,
mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior));
+ mLongPressOnPowerAssistantTimeoutMs = Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnPowerDurationMs));
mVeryLongPressOnPowerBehavior = Settings.Global.getInt(resolver,
Settings.Global.POWER_BUTTON_VERY_LONG_PRESS,
mContext.getResources().getInteger(
@@ -5329,6 +5349,9 @@
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mLongPressOnPowerAssistantTimeoutMs=");
+ pw.println(mLongPressOnPowerAssistantTimeoutMs);
+ pw.print(prefix);
pw.print("mVeryLongPressOnPowerBehavior=");
pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
pw.print(prefix);
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 1ef2bf9..6fee69b 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -44,9 +44,6 @@
private static final int MSG_KEY_VERY_LONG_PRESS = 1;
private static final int MSG_KEY_DELAYED_PRESS = 2;
- private final long mLongPressTimeout;
- private final long mVeryLongPressTimeout;
-
private volatile int mKeyPressCounter;
private boolean mBeganFromNonInteractive = false;
@@ -86,12 +83,19 @@
* </pre>
*/
abstract static class SingleKeyRule {
+
private final int mKeyCode;
private final int mSupportedGestures;
+ private final long mDefaultLongPressTimeout;
+ private final long mDefaultVeryLongPressTimeout;
- SingleKeyRule(int keyCode, @KeyGestureFlag int supportedGestures) {
+ SingleKeyRule(Context context, int keyCode, @KeyGestureFlag int supportedGestures) {
mKeyCode = keyCode;
mSupportedGestures = supportedGestures;
+ mDefaultLongPressTimeout =
+ ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
+ mDefaultVeryLongPressTimeout = context.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout);
}
/**
@@ -134,10 +138,28 @@
*/
void onMultiPress(long downTime, int count) {}
/**
+ * Returns the timeout in milliseconds for a long press.
+ *
+ * If multipress is also supported, this should always be greater than the multipress
+ * timeout. If very long press is supported, this should always be less than the very long
+ * press timeout.
+ */
+ long getLongPressTimeoutMs() {
+ return mDefaultLongPressTimeout;
+ }
+ /**
* Callback when long press has been detected.
*/
void onLongPress(long eventTime) {}
/**
+ * Returns the timeout in milliseconds for a very long press.
+ *
+ * If long press is supported, this should always be longer than the long press timeout.
+ */
+ long getVeryLongPressTimeoutMs() {
+ return mDefaultVeryLongPressTimeout;
+ }
+ /**
* Callback when very long press has been detected.
*/
void onVeryLongPress(long eventTime) {}
@@ -151,10 +173,7 @@
}
}
- public SingleKeyGestureDetector(Context context) {
- mLongPressTimeout = ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
- mVeryLongPressTimeout = context.getResources().getInteger(
- com.android.internal.R.integer.config_veryLongPressTimeout);
+ public SingleKeyGestureDetector() {
mHandler = new KeyHandler();
}
@@ -225,14 +244,14 @@
final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
eventTime);
msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, mLongPressTimeout);
+ mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs());
}
if (mActiveRule.supportVeryLongPress()) {
final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
eventTime);
msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout);
+ mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs());
}
} else {
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 7555a7f..c91d8de 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -96,6 +96,7 @@
private static final int MSG_BROADCAST_ENHANCED_PREDICTION = 4;
private static final int MSG_PROFILE_TIMED_OUT = 5;
private static final int MSG_WIRED_CHARGING_STARTED = 6;
+ private static final int MSG_SCREEN_POLICY = 7;
private static final long[] CHARGING_VIBRATION_TIME = {
40, 40, 40, 40, 40, 40, 40, 40, 40, // ramp-up sampling rate = 40ms
@@ -120,6 +121,7 @@
private final SuspendBlocker mSuspendBlocker;
private final WindowManagerPolicy mPolicy;
private final FaceDownDetector mFaceDownDetector;
+ private final ScreenUndimDetector mScreenUndimDetector;
private final ActivityManagerInternal mActivityManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final InputMethodManagerInternal mInputMethodManagerInternal;
@@ -167,13 +169,14 @@
public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
mContext = context;
mBatteryStats = batteryStats;
mAppOps = mContext.getSystemService(AppOpsManager.class);
mSuspendBlocker = suspendBlocker;
mPolicy = policy;
mFaceDownDetector = faceDownDetector;
+ mScreenUndimDetector = screenUndimDetector;
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
@@ -620,6 +623,22 @@
}
/**
+ * Called when the screen policy changes.
+ */
+ public void onScreenPolicyUpdate(int newPolicy) {
+ if (DEBUG) {
+ Slog.d(TAG, "onScreenPolicyUpdate: newPolicy=" + newPolicy);
+ }
+
+ synchronized (mLock) {
+ Message msg = mHandler.obtainMessage(MSG_SCREEN_POLICY);
+ msg.arg1 = newPolicy;
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /**
* Dumps data for bugreports.
*
* @param pw The stream to print to.
@@ -659,6 +678,7 @@
tm.notifyUserActivity();
mPolicy.userActivity();
mFaceDownDetector.userActivity(event);
+ mScreenUndimDetector.userActivity();
}
void postEnhancedDischargePredictionBroadcast(long delayMs) {
@@ -812,6 +832,10 @@
mSuspendBlocker.release();
}
+ private void screenPolicyChanging(int screenPolicy) {
+ mScreenUndimDetector.recordScreenPolicy(screenPolicy);
+ }
+
private void lockProfile(@UserIdInt int userId) {
mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
}
@@ -852,6 +876,9 @@
case MSG_WIRED_CHARGING_STARTED:
showWiredChargingStarted(msg.arg1);
break;
+ case MSG_SCREEN_POLICY:
+ screenPolicyChanging(msg.arg1);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 73bce31..e7e0425 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -274,6 +274,7 @@
private final BatterySavingStats mBatterySavingStats;
private final AttentionDetector mAttentionDetector;
private final FaceDownDetector mFaceDownDetector;
+ private final ScreenUndimDetector mScreenUndimDetector;
private final BinderService mBinderService;
private final LocalService mLocalService;
private final NativeWrapper mNativeWrapper;
@@ -837,9 +838,10 @@
static class Injector {
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return new Notifier(
- looper, context, batteryStats, suspendBlocker, policy, faceDownDetector);
+ looper, context, batteryStats, suspendBlocker, policy, faceDownDetector,
+ screenUndimDetector);
}
SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
@@ -960,6 +962,7 @@
mInjector.createAmbientDisplaySuppressionController(context);
mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
mFaceDownDetector = new FaceDownDetector(this::onFlip);
+ mScreenUndimDetector = new ScreenUndimDetector();
mBatterySavingStats = new BatterySavingStats(mLock);
mBatterySaverPolicy =
@@ -1146,7 +1149,7 @@
mBatteryStats = BatteryStatsService.getService();
mNotifier = mInjector.createNotifier(Looper.getMainLooper(), mContext, mBatteryStats,
mInjector.createSuspendBlocker(this, "PowerManagerService.Broadcasts"),
- mPolicy, mFaceDownDetector);
+ mPolicy, mFaceDownDetector, mScreenUndimDetector);
mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
mInjector.createSuspendBlocker(
@@ -1181,6 +1184,7 @@
mBatterySaverController.systemReady();
mBatterySaverPolicy.systemReady();
mFaceDownDetector.systemReady(mContext);
+ mScreenUndimDetector.systemReady(mContext);
// Register for settings changes.
resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -3190,6 +3194,7 @@
final boolean ready = mDisplayManagerInternal.requestPowerState(groupId,
displayPowerRequest, mRequestWaitForNegativeProximity);
+ mNotifier.onScreenPolicyUpdate(displayPowerRequest.policy);
if (DEBUG_SPEW) {
Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready
diff --git a/services/core/java/com/android/server/power/ScreenUndimDetector.java b/services/core/java/com/android/server/power/ScreenUndimDetector.java
new file mode 100644
index 0000000..951bc1f
--- /dev/null
+++ b/services/core/java/com/android/server/power/ScreenUndimDetector.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2020 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.power;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Detects when user manually undims the screen (x times) and acquires a wakelock to keep the screen
+ * on temporarily (without changing the screen timeout setting).
+ */
+public class ScreenUndimDetector {
+ private static final String TAG = "ScreenUndimDetector";
+ private static final boolean DEBUG = false;
+
+ private static final String UNDIM_DETECTOR_WAKE_LOCK = "UndimDetectorWakeLock";
+
+ /** DeviceConfig flag: is keep screen on feature enabled. */
+ static final String KEY_KEEP_SCREEN_ON_ENABLED = "keep_screen_on_enabled";
+ private static final boolean DEFAULT_KEEP_SCREEN_ON_ENABLED = true;
+ private static final int OUTCOME_POWER_BUTTON =
+ FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__POWER_BUTTON;
+ private static final int OUTCOME_TIMEOUT =
+ FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__TIMEOUT;
+ private boolean mKeepScreenOnEnabled;
+
+ /** DeviceConfig flag: how long should we keep the screen on. */
+ @VisibleForTesting
+ static final String KEY_KEEP_SCREEN_ON_FOR_MILLIS = "keep_screen_on_for_millis";
+ @VisibleForTesting
+ static final long DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS = TimeUnit.MINUTES.toMillis(10);
+ private long mKeepScreenOnForMillis;
+
+ /** DeviceConfig flag: how many user undims required to trigger keeping the screen on. */
+ @VisibleForTesting
+ static final String KEY_UNDIMS_REQUIRED = "undims_required";
+ @VisibleForTesting
+ static final int DEFAULT_UNDIMS_REQUIRED = 2;
+ private int mUndimsRequired;
+
+ /**
+ * DeviceConfig flag: what is the maximum duration between undims to still consider them
+ * consecutive.
+ */
+ @VisibleForTesting
+ static final String KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS =
+ "max_duration_between_undims_millis";
+ @VisibleForTesting
+ static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = TimeUnit.MINUTES.toMillis(5);
+ private long mMaxDurationBetweenUndimsMillis;
+
+ @VisibleForTesting
+ PowerManager.WakeLock mWakeLock;
+
+ @VisibleForTesting
+ int mCurrentScreenPolicy;
+ @VisibleForTesting
+ int mUndimCounter = 0;
+ @VisibleForTesting
+ long mUndimCounterStartedMillis;
+ private long mUndimOccurredTime = -1;
+ private long mInteractionAfterUndimTime = -1;
+ private InternalClock mClock;
+
+ public ScreenUndimDetector() {
+ mClock = new InternalClock();
+ }
+
+ ScreenUndimDetector(InternalClock clock) {
+ mClock = clock;
+ }
+
+ static class InternalClock {
+ public long getCurrentTime() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ /** Should be called in parent's systemReady() */
+ public void systemReady(Context context) {
+ readValuesFromDeviceConfig();
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ context.getMainExecutor(),
+ (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+ final PowerManager powerManager = context.getSystemService(PowerManager.class);
+ mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+ | PowerManager.ON_AFTER_RELEASE,
+ UNDIM_DETECTOR_WAKE_LOCK);
+ }
+
+ /**
+ * Launches a message that figures out the screen transitions and detects user undims. Must be
+ * called by the parent that is trying to update the screen policy.
+ */
+ public void recordScreenPolicy(int newPolicy) {
+ if (newPolicy == mCurrentScreenPolicy) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Screen policy transition: " + mCurrentScreenPolicy + " -> " + newPolicy);
+ }
+
+ // update the current policy with the new one immediately so we don't accidentally get
+ // into a loop (which is possible if the switch below triggers a new policy).
+ final int currentPolicy = mCurrentScreenPolicy;
+ mCurrentScreenPolicy = newPolicy;
+
+ if (!mKeepScreenOnEnabled) {
+ return;
+ }
+
+ switch (currentPolicy) {
+ case POLICY_DIM:
+ if (newPolicy == POLICY_BRIGHT) {
+ final long now = mClock.getCurrentTime();
+ final long timeElapsedSinceFirstUndim = now - mUndimCounterStartedMillis;
+ if (timeElapsedSinceFirstUndim >= mMaxDurationBetweenUndimsMillis) {
+ reset();
+ }
+ if (mUndimCounter == 0) {
+ mUndimCounterStartedMillis = now;
+ }
+
+ mUndimCounter++;
+
+ if (DEBUG) {
+ Slog.d(TAG, "User undim, counter=" + mUndimCounter
+ + " (required=" + mUndimsRequired + ")"
+ + ", timeElapsedSinceFirstUndim=" + timeElapsedSinceFirstUndim
+ + " (max=" + mMaxDurationBetweenUndimsMillis + ")");
+ }
+ if (mUndimCounter >= mUndimsRequired) {
+ reset();
+ if (DEBUG) {
+ Slog.d(TAG, "Acquiring a wake lock for " + mKeepScreenOnForMillis);
+ }
+ if (mWakeLock != null) {
+ mUndimOccurredTime = mClock.getCurrentTime();
+ mWakeLock.acquire(mKeepScreenOnForMillis);
+ }
+ }
+ } else {
+ if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
+ checkAndLogUndim(OUTCOME_TIMEOUT);
+ }
+ reset();
+ }
+ break;
+ case POLICY_BRIGHT:
+ if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
+ checkAndLogUndim(OUTCOME_POWER_BUTTON);
+ }
+ if (newPolicy != POLICY_DIM) {
+ reset();
+ }
+ break;
+ }
+ }
+
+ @VisibleForTesting
+ void reset() {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting the undim detector");
+ }
+ mUndimCounter = 0;
+ mUndimCounterStartedMillis = 0;
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
+ private boolean readKeepScreenOnNotificationEnabled() {
+ return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_ENABLED,
+ DEFAULT_KEEP_SCREEN_ON_ENABLED);
+ }
+
+ private long readKeepScreenOnForMillis() {
+ return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_FOR_MILLIS,
+ DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS);
+ }
+
+ private int readUndimsRequired() {
+ int undimsRequired = DeviceConfig.getInt(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ DEFAULT_UNDIMS_REQUIRED);
+
+ if (undimsRequired < 1 || undimsRequired > 5) {
+ Slog.e(TAG, "Provided undimsRequired=" + undimsRequired
+ + " is not allowed [1, 5]; using the default=" + DEFAULT_UNDIMS_REQUIRED);
+ return DEFAULT_UNDIMS_REQUIRED;
+ }
+
+ return undimsRequired;
+ }
+
+ private long readMaxDurationBetweenUndimsMillis() {
+ return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
+ DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS);
+ }
+
+ private void onDeviceConfigChange(@NonNull Set<String> keys) {
+ for (String key : keys) {
+ Slog.i(TAG, "onDeviceConfigChange; key=" + key);
+ switch (key) {
+ case KEY_KEEP_SCREEN_ON_ENABLED:
+ case KEY_KEEP_SCREEN_ON_FOR_MILLIS:
+ case KEY_UNDIMS_REQUIRED:
+ case KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS:
+ readValuesFromDeviceConfig();
+ return;
+ default:
+ Slog.i(TAG, "Ignoring change on " + key);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void readValuesFromDeviceConfig() {
+ mKeepScreenOnEnabled = readKeepScreenOnNotificationEnabled();
+ mKeepScreenOnForMillis = readKeepScreenOnForMillis();
+ mUndimsRequired = readUndimsRequired();
+ mMaxDurationBetweenUndimsMillis = readMaxDurationBetweenUndimsMillis();
+
+ Slog.i(TAG, "readValuesFromDeviceConfig():"
+ + "\nmKeepScreenOnForMillis=" + mKeepScreenOnForMillis
+ + "\nmKeepScreenOnNotificationEnabled=" + mKeepScreenOnEnabled
+ + "\nmUndimsRequired=" + mUndimsRequired);
+
+ }
+
+ /**
+ * The user interacted with the screen after an undim, indicating the phone is in use.
+ * We use this event for logging.
+ */
+ public void userActivity() {
+ if (mUndimOccurredTime != 1 && mInteractionAfterUndimTime == -1) {
+ mInteractionAfterUndimTime = mClock.getCurrentTime();
+ }
+ }
+
+ /**
+ * Checks and logs if an undim occurred.
+ *
+ * A log will occur if an undim seems to have resulted in a timeout or a direct screen off such
+ * as from a power button. Some outcomes may not be correctly assigned to a
+ * TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME value.
+ */
+ private void checkAndLogUndim(int outcome) {
+ if (mUndimOccurredTime != -1) {
+ long now = mClock.getCurrentTime();
+ FrameworkStatsLog.write(FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED,
+ outcome,
+ /* time_to_outcome_millis=*/ now - mUndimOccurredTime,
+ /* time_to_first_interaction_millis= */
+ mInteractionAfterUndimTime != -1 ? now - mInteractionAfterUndimTime : -1
+ );
+ mUndimOccurredTime = -1;
+ mInteractionAfterUndimTime = -1;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3a7e13b..abe81e1 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -783,13 +783,14 @@
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed,
- requireConfirmation, userId, opPackageName, operationId, multiSensorConfig);
+ requireConfirmation, userId, operationId, opPackageName, requestId,
+ multiSensorConfig);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7713320..a51ed09 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.wallpaper;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -207,7 +208,7 @@
* wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
* every time the wallpaper is changed.
*/
- private class WallpaperObserver extends FileObserver {
+ class WallpaperObserver extends FileObserver {
final int mUserId;
final WallpaperData mWallpaper;
@@ -225,7 +226,7 @@
mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
}
- private WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+ WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
WallpaperData wallpaper = null;
synchronized (mLock) {
if (lockChanged) {
@@ -308,9 +309,18 @@
}
wallpaper.imageWallpaperPending = false;
if (sysWallpaperChanged) {
+ IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "publish system wallpaper changed!");
+ }
+ notifyWallpaperChanged(wallpaper);
+ }
+ };
// If this was the system wallpaper, rebind...
bindWallpaperComponentLocked(mImageWallpaper, true,
- false, wallpaper, null);
+ false, wallpaper, callback);
notifyColorsWhich |= FLAG_SYSTEM;
}
if (lockWallpaperChanged
@@ -330,15 +340,9 @@
}
saveSettingsLocked(wallpaper.userId);
-
- // Publish completion *after* we've persisted the changes
- if (wallpaper.setComplete != null) {
- try {
- wallpaper.setComplete.onWallpaperChanged();
- } catch (RemoteException e) {
- // if this fails we don't really care; the setting app may just
- // have crashed and that sort of thing is a fact of life.
- }
+ // Notify the client immediately if only lockscreen wallpaper changed.
+ if (lockWallpaperChanged && !sysWallpaperChanged) {
+ notifyWallpaperChanged(wallpaper);
}
}
}
@@ -352,6 +356,18 @@
}
}
+ private void notifyWallpaperChanged(WallpaperData wallpaper) {
+ // Publish completion *after* we've persisted the changes
+ if (wallpaper.setComplete != null) {
+ try {
+ wallpaper.setComplete.onWallpaperChanged();
+ } catch (RemoteException e) {
+ // if this fails we don't really care; the setting app may just
+ // have crashed and that sort of thing is a fact of life.
+ }
+ }
+ }
+
private void notifyLockWallpaperChanged() {
final IWallpaperManagerCallback cb = mKeyguardListener;
if (cb != null) {
@@ -363,7 +379,7 @@
}
}
- private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
+ void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
if (wallpaper.connection != null) {
wallpaper.connection.forEachDisplayConnector(connector -> {
notifyWallpaperColorsChangedOnDisplay(wallpaper, which, connector.mDisplayId);
@@ -567,7 +583,7 @@
* Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
* for display.
*/
- private void generateCrop(WallpaperData wallpaper) {
+ void generateCrop(WallpaperData wallpaper) {
boolean success = false;
// Only generate crop for default display.
@@ -2045,7 +2061,21 @@
}
}
+ private boolean hasCrossUserPermission() {
+ final int interactPermission =
+ mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL);
+ return interactPermission == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
public boolean hasNamedWallpaper(String name) {
+ final int callingUser = UserHandle.getCallingUserId();
+ final boolean allowCrossUser = hasCrossUserPermission();
+ if (DEBUG) {
+ Slog.d(TAG, "hasNamedWallpaper() caller " + Binder.getCallingUid()
+ + " cross-user?: " + allowCrossUser);
+ }
+
synchronized (mLock) {
List<UserInfo> users;
final long ident = Binder.clearCallingIdentity();
@@ -2055,6 +2085,11 @@
Binder.restoreCallingIdentity(ident);
}
for (UserInfo user: users) {
+ if (!allowCrossUser && callingUser != user.id) {
+ // No cross-user information for callers without permission
+ continue;
+ }
+
// ignore managed profiles
if (user.isManagedProfile()) {
continue;
@@ -2814,7 +2849,7 @@
return false;
}
- private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
+ boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
if (DEBUG_LIVE) {
Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
@@ -3101,7 +3136,7 @@
return new JournaledFile(new File(base), new File(base + ".tmp"));
}
- private void saveSettingsLocked(int userId) {
+ void saveSettingsLocked(int userId) {
JournaledFile journal = makeJournaledFile(userId);
FileOutputStream fstream = null;
try {
@@ -3250,7 +3285,7 @@
* Important: this method loads settings to initialize the given user's wallpaper data if
* there is no current in-memory state.
*/
- private WallpaperData getWallpaperSafeLocked(int userId, int which) {
+ WallpaperData getWallpaperSafeLocked(int userId, int which) {
// We're setting either just system (work with the system wallpaper),
// both (also work with the system wallpaper), or just the lock
// wallpaper (update against the existing lock wallpaper if any).
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3a4faf7..e02e867 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -21,6 +21,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -53,6 +55,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
@@ -64,6 +67,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.service.voice.VoiceInteractionManagerInternal;
import android.util.Slog;
import android.view.RemoteAnimationDefinition;
@@ -74,6 +78,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.uri.NeededUriGrants;
import com.android.server.vr.VrManagerInternal;
@@ -557,20 +562,45 @@
@Override
public int getLaunchedFromUid(IBinder token) {
+ if (!canGetLaunchedFrom()) {
+ return INVALID_UID;
+ }
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- return r != null ? r.launchedFromUid : android.os.Process.INVALID_UID;
+ return r != null ? r.launchedFromUid : INVALID_UID;
}
}
@Override
public String getLaunchedFromPackage(IBinder token) {
+ if (!canGetLaunchedFrom()) {
+ return null;
+ }
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
return r != null ? r.launchedFromPackage : null;
}
}
+ /** Whether the caller can get the package or uid that launched its activity. */
+ private boolean canGetLaunchedFrom() {
+ final int uid = Binder.getCallingUid();
+ if (UserHandle.getAppId(uid) == SYSTEM_UID) {
+ return true;
+ }
+ final PackageManagerInternal pm = mService.mWindowManager.mPmInternal;
+ final AndroidPackage callingPkg = pm.getPackage(uid);
+ if (callingPkg == null) {
+ return false;
+ }
+ if (callingPkg.isSignedWithPlatformKey()) {
+ return true;
+ }
+ final String[] installerNames = pm.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.getUserId(uid));
+ return installerNames.length > 0 && callingPkg.getPackageName().equals(installerNames[0]);
+ }
+
@Override
public void setRequestedOrientation(IBinder token, int requestedOrientation) {
final long origId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c2cfe0b..d13c8ba 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4148,7 +4148,7 @@
// The activity now gets access to the data associated with this Intent.
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants,
getUriPermissionsLocked());
- final ReferrerIntent rintent = new ReferrerIntent(intent, referrer);
+ final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer));
boolean unsent = true;
final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
@@ -8505,6 +8505,19 @@
}
/**
+ * Gets the referrer package name with respect to package visibility. This method returns null
+ * if the given package is not visible to this activity.
+ */
+ String getFilteredReferrer(String referrerPackage) {
+ if (referrerPackage == null || (!referrerPackage.equals(packageName)
+ && mWmService.mPmInternal.filterAppAccess(
+ referrerPackage, info.applicationInfo.uid, mUserId))) {
+ return null;
+ }
+ return referrerPackage;
+ }
+
+ /**
* Determines whether this ActivityRecord can turn the screen on. It checks whether the flag
* {@link ActivityRecord#getTurnScreenOnFlag} is set and checks whether the ActivityRecord
* should be visible depending on Keyguard state.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e3459a1..efa67e9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -848,9 +848,9 @@
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
- r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
- r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
- r.takeOptions(), isTransitionForward,
+ r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
+ proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
+ results, newIntents, r.takeOptions(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken,
r.getLaunchedFromBubble()));
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 73d31bf..d0457b0 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -355,6 +355,8 @@
private PointerLocationView mPointerLocationView;
+ private int mDisplayCutoutTouchableRegionSize;
+
/**
* The area covered by system windows which belong to another display. Forwarded insets is set
* in case this is a virtual display, this is displayed on another display that has insets, and
@@ -1081,8 +1083,21 @@
(displayFrames, windowState, rect) -> {
rect.bottom = rect.top + getStatusBarHeight(displayFrames);
};
+ final TriConsumer<DisplayFrames, WindowState, Rect> gestureFrameProvider =
+ (displayFrames, windowState, rect) -> {
+ rect.bottom = rect.top + getStatusBarHeight(displayFrames);
+ final DisplayCutout cutout =
+ displayFrames.mInsetsState.getDisplayCutout();
+ if (cutout != null) {
+ final Rect top = cutout.getBoundingRectTop();
+ if (!top.isEmpty()) {
+ rect.bottom = rect.bottom + mDisplayCutoutTouchableRegionSize;
+ }
+ }
+ };
mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, frameProvider);
- mDisplayContent.setInsetProvider(ITYPE_TOP_MANDATORY_GESTURES, win, frameProvider);
+ mDisplayContent.setInsetProvider(
+ ITYPE_TOP_MANDATORY_GESTURES, win, gestureFrameProvider);
mDisplayContent.setInsetProvider(ITYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider);
break;
case TYPE_NAVIGATION_BAR:
@@ -1993,11 +2008,14 @@
mStatusBarHeightForRotation[landscapeRotation] =
mStatusBarHeightForRotation[seascapeRotation] =
res.getDimensionPixelSize(R.dimen.status_bar_height_landscape);
+ mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
+ R.dimen.display_cutout_touchable_region_size);
} else {
mStatusBarHeightForRotation[portraitRotation] =
mStatusBarHeightForRotation[upsideDownRotation] =
mStatusBarHeightForRotation[landscapeRotation] =
mStatusBarHeightForRotation[seascapeRotation] = 0;
+ mDisplayCutoutTouchableRegionSize = 0;
}
// Height of the navigation bar when presented horizontally at bottom
@@ -2980,6 +2998,7 @@
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
+ mSystemGestures.dump(pw, prefix);
pw.print(prefix); pw.println("Looper state:");
mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 73d6cec..c9db14d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -444,7 +444,9 @@
}
if (mDisplayContent.mFixedRotationTransitionListener
- .isTopFixedOrientationRecentsAnimating()) {
+ .isTopFixedOrientationRecentsAnimating()
+ // If screen is off or the device is going to sleep, then still allow to update.
+ && mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
// During the recents animation, the closing app might still be considered on top.
// In order to ignore its requested orientation to avoid a sensor led rotation (e.g
// user rotating the device while the recents animation is running), we ignore
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 18ea738b..aa257f8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -464,7 +464,8 @@
if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) {
// Only allow the extras to be dispatched to a global-intercepting drag target
ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
- DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, touchX, touchY,
+ DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
+ newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
data, false /* includeDragSurface */,
null /* dragAndDropPermission */);
try {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8c781a1..d417d56 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -39,6 +39,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -650,6 +651,7 @@
|| type == TYPE_DOCK_DIVIDER
|| type == TYPE_ACCESSIBILITY_OVERLAY
|| type == TYPE_INPUT_CONSUMER
- || type == TYPE_VOICE_INTERACTION;
+ || type == TYPE_VOICE_INTERACTION
+ || type == TYPE_STATUS_BAR_ADDITIONAL;
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bd688a6..52da4b8 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2125,6 +2125,9 @@
final Task rootTask;
if (singleActivity) {
rootTask = task;
+
+ // Apply the last recents animation leash transform to the task entering PIP
+ rootTask.maybeApplyLastRecentsAnimationTransaction();
} else {
// In the case of multiple activities, we will create a new task for it and then
// move the PIP activity into the task. Note that we explicitly defer the task
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3c6c23b..c7bf8ec 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -59,11 +59,30 @@
@VisibleForTesting
final Animatable mAnimatable;
private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
+
+ /**
+ * Static callback to run on all animations started through this SurfaceAnimator
+ * when an animation on a Surface is finished or cancelled without restart.
+ */
@VisibleForTesting
@Nullable
final OnAnimationFinishedCallback mStaticAnimationFinishedCallback;
+
+ /**
+ * Callback unique to each animation (i.e. AnimationAdapter). To be run when an animation on a
+ * Surface is finished or cancelled without restart.
+ */
@Nullable
- private OnAnimationFinishedCallback mAnimationFinishedCallback;
+ private OnAnimationFinishedCallback mSurfaceAnimationFinishedCallback;
+
+ /**
+ * The callback is triggered after the SurfaceAnimator sends a cancel call to the underlying
+ * AnimationAdapter.
+ * NOTE: Must be called wherever we call onAnimationCancelled on mAnimation.
+ */
+ @Nullable
+ private Runnable mAnimationCancelledCallback;
+
private boolean mAnimationStartDelayed;
/**
@@ -100,7 +119,7 @@
return;
}
final OnAnimationFinishedCallback animationFinishCallback =
- mAnimationFinishedCallback;
+ mSurfaceAnimationFinishedCallback;
reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */);
if (staticAnimationFinishedCallback != null) {
staticAnimationFinishedCallback.onAnimationFinished(type, anim);
@@ -130,15 +149,19 @@
* This is important as it will start with the leash hidden or visible before
* handing it to the component that is responsible to run the animation.
* @param animationFinishedCallback The callback being triggered when the animation finishes.
+ * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
+ * cancel call to the underlying AnimationAdapter.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
+ @Nullable Runnable animationCancelledCallback,
@Nullable SurfaceFreezer freezer) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
mAnimationType = type;
- mAnimationFinishedCallback = animationFinishedCallback;
+ mSurfaceAnimationFinishedCallback = animationFinishedCallback;
+ mAnimationCancelledCallback = animationCancelledCallback;
final SurfaceControl surface = mAnimatable.getSurfaceControl();
if (surface == null) {
Slog.w(TAG, "Unable to start animation, surface is null or no children.");
@@ -161,14 +184,9 @@
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
- @AnimationType int type,
- @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
- startAnimation(t, anim, hidden, type, animationFinishedCallback, null /* freezer */);
- }
-
- void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
- startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
+ startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */,
+ null /* animationCancelledCallback */, null /* freezer */);
}
/**
@@ -278,7 +296,8 @@
mLeash = from.mLeash;
mAnimation = from.mAnimation;
mAnimationType = from.mAnimationType;
- mAnimationFinishedCallback = from.mAnimationFinishedCallback;
+ mSurfaceAnimationFinishedCallback = from.mSurfaceAnimationFinishedCallback;
+ mAnimationCancelledCallback = from.mAnimationCancelledCallback;
// Cancel source animation, but don't let animation runner cancel the animation.
from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
@@ -306,11 +325,16 @@
final SurfaceControl leash = mLeash;
final AnimationAdapter animation = mAnimation;
final @AnimationType int animationType = mAnimationType;
- final OnAnimationFinishedCallback animationFinishedCallback = mAnimationFinishedCallback;
+ final OnAnimationFinishedCallback animationFinishedCallback =
+ mSurfaceAnimationFinishedCallback;
+ final Runnable animationCancelledCallback = mAnimationCancelledCallback;
reset(t, false);
if (animation != null) {
if (!mAnimationStartDelayed && forwardCancel) {
animation.onAnimationCancelled(leash);
+ if (animationCancelledCallback != null) {
+ animationCancelledCallback.run();
+ }
}
if (!restarting) {
if (mStaticAnimationFinishedCallback != null) {
@@ -335,7 +359,7 @@
private void reset(Transaction t, boolean destroyLeash) {
mService.mAnimationTransferMap.remove(mAnimation);
mAnimation = null;
- mAnimationFinishedCallback = null;
+ mSurfaceAnimationFinishedCallback = null;
mAnimationType = ANIMATION_TYPE_NONE;
if (mLeash == null) {
return;
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 513b1b7..658f4ef 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -16,6 +16,12 @@
package com.android.server.wm;
+import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
+import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
+import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
+import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
+
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -33,6 +39,8 @@
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.widget.OverScroller;
+import java.io.PrintWriter;
+
/**
* Listens for system-wide input gestures, firing callbacks when detected.
* @hide
@@ -54,7 +62,8 @@
private final Context mContext;
private final Handler mHandler;
private int mDisplayCutoutTouchableRegionSize;
- private int mSwipeStartThreshold;
+ // The thresholds for each edge of the display
+ private final Rect mSwipeStartThreshold = new Rect();
private int mSwipeDistanceThreshold;
private final Callbacks mCallbacks;
private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
@@ -66,7 +75,6 @@
int screenHeight;
int screenWidth;
- private DisplayInfo mTmpDisplayInfo = new DisplayInfo();
private int mDownPointers;
private boolean mSwipeFireable;
private boolean mDebugFireable;
@@ -88,27 +96,41 @@
void onConfigurationChanged() {
final Resources r = mContext.getResources();
+ final int defaultThreshold = r.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_start_threshold);
+ mSwipeStartThreshold.set(defaultThreshold, defaultThreshold, defaultThreshold,
+ defaultThreshold);
+ mSwipeDistanceThreshold = defaultThreshold;
+
final Display display = DisplayManagerGlobal.getInstance()
.getRealDisplay(Display.DEFAULT_DISPLAY);
- display.getDisplayInfo(mTmpDisplayInfo);
- mSwipeStartThreshold = mTmpDisplayInfo.logicalWidth > mTmpDisplayInfo.logicalHeight
- ? r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_landscape)
- : r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_portrait);
-
final DisplayCutout displayCutout = display.getCutout();
if (displayCutout != null) {
- final Rect bounds = displayCutout.getBoundingRectTop();
- if (!bounds.isEmpty()) {
- // Expand swipe start threshold such that we can catch touches that just start below
- // the notch area
- mDisplayCutoutTouchableRegionSize = r.getDimensionPixelSize(
- com.android.internal.R.dimen.display_cutout_touchable_region_size);
- mSwipeStartThreshold += mDisplayCutoutTouchableRegionSize;
+ // Expand swipe start threshold such that we can catch touches that just start beyond
+ // the notch area
+ mDisplayCutoutTouchableRegionSize = r.getDimensionPixelSize(
+ com.android.internal.R.dimen.display_cutout_touchable_region_size);
+ final Rect[] bounds = displayCutout.getBoundingRectsAll();
+ if (bounds[BOUNDS_POSITION_LEFT] != null) {
+ mSwipeStartThreshold.left = Math.max(mSwipeStartThreshold.left,
+ bounds[BOUNDS_POSITION_LEFT].width() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_TOP] != null) {
+ mSwipeStartThreshold.top = Math.max(mSwipeStartThreshold.top,
+ bounds[BOUNDS_POSITION_TOP].height() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_RIGHT] != null) {
+ mSwipeStartThreshold.right = Math.max(mSwipeStartThreshold.right,
+ bounds[BOUNDS_POSITION_RIGHT].width() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_BOTTOM] != null) {
+ mSwipeStartThreshold.bottom = Math.max(mSwipeStartThreshold.bottom,
+ bounds[BOUNDS_POSITION_BOTTOM].height()
+ + mDisplayCutoutTouchableRegionSize);
}
}
- mSwipeDistanceThreshold = mSwipeStartThreshold;
if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold
- + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
+ + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
}
private static <T> T checkNull(String name, T arg) {
@@ -275,22 +297,22 @@
final long elapsed = time - mDownTime[i];
if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
+ " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
- if (fromY <= mSwipeStartThreshold
+ if (fromY <= mSwipeStartThreshold.top
&& y > fromY + mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_TOP;
}
- if (fromY >= screenHeight - mSwipeStartThreshold
+ if (fromY >= screenHeight - mSwipeStartThreshold.bottom
&& y < fromY - mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_BOTTOM;
}
- if (fromX >= screenWidth - mSwipeStartThreshold
+ if (fromX >= screenWidth - mSwipeStartThreshold.right
&& x < fromX - mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_RIGHT;
}
- if (fromX <= mSwipeStartThreshold
+ if (fromX <= mSwipeStartThreshold.left
&& x > fromX + mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_LEFT;
@@ -298,6 +320,15 @@
return SWIPE_NONE;
}
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final String inner = prefix + " ";
+ pw.println(prefix + TAG + ":");
+ pw.print(inner); pw.print("mDisplayCutoutTouchableRegionSize=");
+ pw.println(mDisplayCutoutTouchableRegionSize);
+ pw.print(inner); pw.print("mSwipeStartThreshold="); pw.println(mSwipeStartThreshold);
+ pw.print(inner); pw.print("mSwipeDistanceThreshold="); pw.println(mSwipeDistanceThreshold);
+ }
+
private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener {
private OverScroller mOverscroller;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index d450dbf..ee4c629 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -42,6 +42,9 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static java.lang.Integer.MIN_VALUE;
+
+import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
@@ -80,6 +83,22 @@
DisplayContent mDisplayContent;
/**
+ * A color layer that serves as a solid color background to certain animations.
+ */
+ private SurfaceControl mColorBackgroundLayer;
+
+ /**
+ * This counter is used to make sure we don't prematurely clear the background color in the
+ * case that background color animations are interleaved.
+ * NOTE: The last set color will remain until the counter is reset to 0, which means that an
+ * animation background color may sometime remain after the animation has finished through an
+ * animation with a different background color if an animation starts after and ends before
+ * another where both set different background colors. However, this is not a concern as
+ * currently all task animation backgrounds are the same color.
+ */
+ private int mColorLayerCounter = 0;
+
+ /**
* A control placed at the appropriate level for transitions to occur.
*/
private SurfaceControl mAppAnimationLayer;
@@ -961,6 +980,11 @@
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
if (getParent() != null) {
super.onParentChanged(newParent, oldParent, () -> {
+ mColorBackgroundLayer = makeChildSurface(null)
+ .setColorLayer()
+ .setName("colorBackgroundLayer")
+ .setCallsite("TaskDisplayArea.onParentChanged")
+ .build();
mAppAnimationLayer = makeChildSurface(null)
.setName("animationLayer")
.setCallsite("TaskDisplayArea.onParentChanged")
@@ -977,6 +1001,7 @@
.setName("splitScreenDividerAnchor")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
+
getSyncTransaction()
.show(mAppAnimationLayer)
.show(mBoostedAppAnimationLayer)
@@ -986,11 +1011,13 @@
} else {
super.onParentChanged(newParent, oldParent);
mWmService.mTransactionFactory.get()
+ .remove(mColorBackgroundLayer)
.remove(mAppAnimationLayer)
.remove(mBoostedAppAnimationLayer)
.remove(mHomeAppAnimationLayer)
.remove(mSplitScreenDividerAnchor)
.apply();
+ mColorBackgroundLayer = null;
mAppAnimationLayer = null;
mBoostedAppAnimationLayer = null;
mHomeAppAnimationLayer = null;
@@ -998,6 +1025,39 @@
}
}
+ void setBackgroundColor(@ColorInt int color) {
+ if (mColorBackgroundLayer == null) {
+ return;
+ }
+
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color >> 0) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+
+ mColorLayerCounter++;
+
+ getPendingTransaction().setLayer(mColorBackgroundLayer, MIN_VALUE)
+ .setColor(mColorBackgroundLayer, new float[]{r, g, b})
+ .setAlpha(mColorBackgroundLayer, a)
+ .setWindowCrop(mColorBackgroundLayer, getSurfaceWidth(), getSurfaceHeight())
+ .setPosition(mColorBackgroundLayer, 0, 0)
+ .show(mColorBackgroundLayer);
+
+ scheduleAnimation();
+ }
+
+ void clearBackgroundColor() {
+ mColorLayerCounter--;
+
+ // Only clear the color layer if we have received the same amounts of clear as set
+ // requests.
+ if (mColorLayerCounter == 0) {
+ getPendingTransaction().hide(mColorBackgroundLayer);
+ scheduleAnimation();
+ }
+ }
+
@Override
void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
super.migrateToNewSurfaceControl(t);
@@ -1006,6 +1066,7 @@
}
// As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
+ t.reparent(mColorBackgroundLayer, mSurfaceControl);
t.reparent(mAppAnimationLayer, mSurfaceControl);
t.reparent(mBoostedAppAnimationLayer, mSurfaceControl);
t.reparent(mHomeAppAnimationLayer, mSurfaceControl);
@@ -2149,6 +2210,11 @@
}
@Override
+ TaskDisplayArea getTaskDisplayArea() {
+ return this;
+ }
+
+ @Override
boolean isTaskDisplayArea() {
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index ec4eb5d..073a508 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -118,16 +118,30 @@
@Override
public long calculateStatusBarTransitionStartTime() {
TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
- if (openTranslateAnimation != null) {
- // Some interpolators are extremely quickly mostly finished, but not completely. For
- // our purposes, we need to find the fraction for which ther interpolator is mostly
- // there, and use that value for the calculation.
- float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
- return SystemClock.uptimeMillis()
- + openTranslateAnimation.getStartOffset()
- + (long)(openTranslateAnimation.getDuration() * t)
- - STATUS_BAR_TRANSITION_DURATION;
+ if (openTranslateAnimation != null) {
+ if (openTranslateAnimation.isXAxisTransition()
+ && openTranslateAnimation.isFullWidthTranslate()) {
+ // On X axis transitions that are fullscreen (heuristic for task like transitions)
+ // we want the status bar to animate right in the middle of the translation when
+ // the windows/tasks have each moved half way across.
+ float t = findMiddleOfTranslationFraction(openTranslateAnimation.getInterpolator());
+
+ return SystemClock.uptimeMillis()
+ + openTranslateAnimation.getStartOffset()
+ + (long) (openTranslateAnimation.getDuration() * t)
+ - (long) (STATUS_BAR_TRANSITION_DURATION * 0.5);
+ } else {
+ // Some interpolators are extremely quickly mostly finished, but not completely. For
+ // our purposes, we need to find the fraction for which their interpolator is mostly
+ // there, and use that value for the calculation.
+ float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
+
+ return SystemClock.uptimeMillis()
+ + openTranslateAnimation.getStartOffset()
+ + (long) (openTranslateAnimation.getDuration() * t)
+ - STATUS_BAR_TRANSITION_DURATION;
+ }
} else {
return SystemClock.uptimeMillis();
}
@@ -176,20 +190,39 @@
}
/**
- * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
- * {@code interpolator(t + eps) > 0.99}.
+ * Finds the fraction of the animation's duration at which the transition is almost done with a
+ * maximal error of 0.01 when it is animated with {@code interpolator}.
*/
private static float findAlmostThereFraction(Interpolator interpolator) {
+ return findInterpolationAdjustedTargetFraction(interpolator, 0.99f, 0.01f);
+ }
+
+ /**
+ * Finds the fraction of the animation's duration at which the transition is spacially half way
+ * done with a maximal error of 0.01 when it is animated with {@code interpolator}.
+ */
+ private float findMiddleOfTranslationFraction(Interpolator interpolator) {
+ return findInterpolationAdjustedTargetFraction(interpolator, 0.5f, 0.01f);
+ }
+
+ /**
+ * Binary searches for a {@code val} such that there exists an {@code -0.01 < epsilon < 0.01}
+ * for which {@code interpolator(val + epsilon) > target}.
+ */
+ private static float findInterpolationAdjustedTargetFraction(
+ Interpolator interpolator, float target, float epsilon) {
float val = 0.5f;
float adj = 0.25f;
- while (adj >= 0.01f) {
- if (interpolator.getInterpolation(val) < 0.99f) {
+
+ while (adj >= epsilon) {
+ if (interpolator.getInterpolation(val) < target) {
val += adj;
} else {
val -= adj;
}
adj /= 2;
}
+
return val;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b1c7e19..d99aed1 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -30,6 +30,10 @@
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -59,10 +63,13 @@
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
+import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.app.WindowConfiguration;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
@@ -88,6 +95,7 @@
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
@@ -850,6 +858,12 @@
return parent != null ? parent.getRootDisplayArea() : null;
}
+ @Nullable
+ TaskDisplayArea getTaskDisplayArea() {
+ WindowContainer parent = getParent();
+ return parent != null ? parent.getTaskDisplayArea() : null;
+ }
+
boolean isAttached() {
return getDisplayArea() != null;
}
@@ -2495,10 +2509,13 @@
* some point but the meaning is too weird to work for all containers.
* @param type The type of animation defined as {@link AnimationType}.
* @param animationFinishedCallback The callback being triggered when the animation finishes.
+ * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
+ * cancel call to the underlying AnimationAdapter.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
- @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+ @Nullable OnAnimationFinishedCallback animationFinishedCallback,
+ @Nullable Runnable animationCancelledCallback) {
if (DEBUG_ANIM) {
Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
}
@@ -2506,7 +2523,14 @@
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
- mSurfaceFreezer);
+ animationCancelledCallback, mSurfaceFreezer);
+ }
+
+ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+ @AnimationType int type,
+ @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+ startAnimation(t, anim, hidden, type, animationFinishedCallback,
+ null /* adapterAnimationCancelledCallback */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@@ -2714,8 +2738,26 @@
if (sources != null) {
mSurfaceAnimationSources.addAll(sources);
}
+
+ TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
+ boolean isSettingBackgroundColor = taskDisplayArea != null
+ && isTransitionWithBackgroundColor(transit);
+
+ if (isSettingBackgroundColor) {
+ Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+ @ColorInt int backgroundColor = uiContext.getColor(R.color.overview_background);
+
+ taskDisplayArea.setBackgroundColor(backgroundColor);
+ }
+
+ final Runnable cleanUpCallback = isSettingBackgroundColor
+ ? taskDisplayArea::clearBackgroundColor : () -> {};
+
startAnimation(getPendingTransaction(), adapter, !isVisible(),
- ANIMATION_TYPE_APP_TRANSITION);
+ ANIMATION_TYPE_APP_TRANSITION,
+ (type, anim) -> cleanUpCallback.run(),
+ cleanUpCallback);
+
if (adapter.getShowWallpaper()) {
getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
@@ -2726,6 +2768,13 @@
}
}
+ private boolean isTransitionWithBackgroundColor(@TransitionOldType int transit) {
+ return transit == TRANSIT_OLD_TASK_OPEN
+ || transit == TRANSIT_OLD_TASK_CLOSE
+ || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_TASK_TO_BACK;
+ }
+
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
return mWmService.mSurfaceAnimationRunner;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9caef70..e3b25a5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2717,8 +2717,8 @@
}
@Override
- public boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
- Bundle options) {
+ public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int
+ type, int displayId, Bundle options) {
final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
"attachWindowContextToDisplayArea", false /* printLog */);
final int callingUid = Binder.getCallingUid();
@@ -2729,15 +2729,17 @@
if (dc == null) {
ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayArea: trying to attach"
+ " to a non-existing display:%d", displayId);
- return false;
+ return null;
}
// TODO(b/155340867): Investigate if we still need roundedCornerOverlay after
// the feature b/155340867 is completed.
final DisplayArea da = dc.findAreaForWindowType(type, options,
callerCanManageAppTokens, false /* roundedCornerOverlay */);
+ // TODO(b/190019118): Avoid to send onConfigurationChanged because it has been done
+ // in return value of attachWindowContextToDisplayArea.
mWindowContextListenerController.registerWindowContainerListener(clientToken, da,
callingUid, type, options);
- return true;
+ return da.getConfiguration();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7070,6 +7072,7 @@
"requestScrollCapture: caught exception dispatching to window."
+ "token=%s", targetWindow.mClient.asBinder());
responseBuilder.setWindowTitle(targetWindow.getName());
+ responseBuilder.setPackageName(targetWindow.getOwningPackage());
responseBuilder.setDescription(String.format("caught exception: %s", e));
listener.onScrollCaptureResponse(responseBuilder.build());
}
@@ -8597,8 +8600,9 @@
if (imeTargetWindowTask == null) {
return false;
}
- final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId,
- false /* isLowResolution */);
+ final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
+ imeTargetWindowTask.mUserId, false /* isLowResolution */,
+ false /* restoreFromDisk */);
return snapshot != null && snapshot.hasImeSurface();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5e042ef..0af6a29 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -26,7 +26,6 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
-import static android.hardware.input.InputManager.BLOCK_UNTRUSTED_TOUCHES;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -194,7 +193,6 @@
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
-import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -1156,9 +1154,6 @@
}
int getTouchOcclusionMode() {
- if (!CompatChanges.isChangeEnabled(BLOCK_UNTRUSTED_TOUCHES, mOwnerUid)) {
- return TouchOcclusionMode.ALLOW;
- }
if (WindowManager.LayoutParams.isSystemAlertWindowType(mAttrs.type)) {
return TouchOcclusionMode.USE_OPACITY;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a94ad4a..bb9740b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -337,7 +337,7 @@
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
- void setPointerCapture(bool enabled) override;
+ void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
/* --- PointerControllerPolicyInterface implementation --- */
@@ -372,8 +372,8 @@
// Show touches feature enable/disable.
bool showTouches;
- // Pointer capture feature enable/disable.
- bool pointerCapture;
+ // The latest request to enable or disable Pointer Capture.
+ PointerCaptureRequest pointerCaptureRequest;
// Sprite controller singleton, created on first use.
sp<SpriteController> spriteController;
@@ -417,7 +417,6 @@
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
- mLocked.pointerCapture = false;
mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
}
mInteractive = true;
@@ -446,7 +445,9 @@
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
- dump += StringPrintf(INDENT "Pointer Capture Enabled: %s\n", toString(mLocked.pointerCapture));
+ dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
+ mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.seq);
}
dump += "\n";
@@ -634,7 +635,7 @@
outConfig->showTouches = mLocked.showTouches;
- outConfig->pointerCapture = mLocked.pointerCapture;
+ outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
outConfig->setDisplayViewports(mLocked.viewports);
@@ -1383,16 +1384,16 @@
checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus");
}
-void NativeInputManager::setPointerCapture(bool enabled) {
+void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) {
{ // acquire lock
AutoMutex _l(mLock);
- if (mLocked.pointerCapture == enabled) {
+ if (mLocked.pointerCaptureRequest == request) {
return;
}
- ALOGV("%s pointer capture.", enabled ? "Enabling" : "Disabling");
- mLocked.pointerCapture = enabled;
+ ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ mLocked.pointerCaptureRequest = request;
} // release lock
mInputManager->getReader()->requestRefreshConfiguration(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ac344d6..9ceabce 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3599,6 +3599,9 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(
+ isCallingFromPackage(adminReceiver.getPackageName(), caller.getUid())
+ || isSystemUid(caller));
synchronized (getLockObject()) {
ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
@@ -12695,74 +12698,21 @@
// This method is called from AM with its lock held, so don't take the DPMS lock.
// b/29242568
- ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
- if (profileOwner != null) {
- return DevicePolicyManagerService.this
- .createShowAdminSupportIntent(profileOwner, userId);
- }
-
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null && deviceOwner.first == userId) {
- return DevicePolicyManagerService.this
- .createShowAdminSupportIntent(deviceOwner.second, userId);
- }
-
- // We're not specifying the device admin because there isn't one.
- if (useDefaultIfNoAdmin) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
+ if (getEnforcingAdminAndUserDetailsInternal(userId, null) != null
+ || useDefaultIfNoAdmin) {
+ return DevicePolicyManagerService.this.createShowAdminSupportIntent(userId);
}
return null;
}
@Override
public Intent createUserRestrictionSupportIntent(int userId, String userRestriction) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- final List<UserManager.EnforcingUser> sources = mUserManager
- .getUserRestrictionSources(userRestriction, UserHandle.of(userId));
- if (sources == null || sources.isEmpty()) {
- // The restriction is not enforced.
- return null;
- } else if (sources.size() > 1) {
- // In this case, we'll show an admin support dialog that does not
- // specify the admin.
- // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
- // the admin for the calling user.
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- null, userId);
- }
- final UserManager.EnforcingUser enforcingUser = sources.get(0);
- final int sourceType = enforcingUser.getUserRestrictionSource();
- final int enforcingUserId = enforcingUser.getUserHandle().getIdentifier();
- if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) {
- // Restriction was enforced by PO
- final ComponentName profileOwner = mOwners.getProfileOwnerComponent(
- enforcingUserId);
- if (profileOwner != null) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- profileOwner, enforcingUserId);
- }
- } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
- // Restriction was enforced by DO
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- deviceOwner.second, deviceOwner.first);
- }
- } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) {
- /*
- * In this case, the user restriction is enforced by the system.
- * So we won't show an admin support intent, even if it is also
- * enforced by a profile/device owner.
- */
- return null;
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
+ Intent intent = null;
+ if (getEnforcingAdminAndUserDetailsInternal(userId, userRestriction) != null) {
+ intent = DevicePolicyManagerService.this.createShowAdminSupportIntent(userId);
+ intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, userRestriction);
}
- return null;
+ return intent;
}
@Override
@@ -13057,53 +13007,153 @@
}
}
- private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
+ private Intent createShowAdminSupportIntent(int userId) {
// This method is called with AMS lock held, so don't take DPMS lock
final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
intent.putExtra(Intent.EXTRA_USER_ID, userId);
- intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
return intent;
}
- @Override
- public Intent createAdminSupportIntent(String restriction) {
- Objects.requireNonNull(restriction);
- final CallerIdentity caller = getCallerIdentity();
- Intent intent = null;
- if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction) ||
- DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ */
+ private Bundle getEnforcingAdminAndUserDetailsInternal(int userId, String restriction) {
+ Bundle result = null;
+ if (restriction == null) {
+ ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+ if (profileOwner != null) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ profileOwner);
+ return result;
+ }
+ final Pair<Integer, ComponentName> deviceOwner =
+ mOwners.getDeviceOwnerUserIdAndComponent();
+ if (deviceOwner != null && deviceOwner.first == userId) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ deviceOwner.second);
+ return result;
+ }
+ } else if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)
+ || DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
synchronized (getLockObject()) {
- final DevicePolicyData policy = getUserData(caller.getUserId());
+ final DevicePolicyData policy = getUserData(userId);
final int N = policy.mAdminList.size();
for (int i = 0; i < N; i++) {
final ActiveAdmin admin = policy.mAdminList.get(i);
if ((admin.disableCamera &&
- DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) ||
- (admin.disableScreenCapture && DevicePolicyManager
- .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
- intent = createShowAdminSupportIntent(admin.info.getComponent(),
- caller.getUserId());
- break;
+ DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction))
+ || (admin.disableScreenCapture && DevicePolicyManager
+ .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ admin.info.getComponent());
+ return result;
}
}
// For the camera, a device owner on a different user can disable it globally,
// so we need an additional check.
- if (intent == null
+ if (result == null
&& DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
final ActiveAdmin admin = getDeviceOwnerAdminLocked();
if (admin != null && admin.disableCamera) {
- intent = createShowAdminSupportIntent(admin.info.getComponent(),
- mOwners.getDeviceOwnerUserId());
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, mOwners.getDeviceOwnerUserId());
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ admin.info.getComponent());
+ return result;
}
}
}
} else {
- // if valid, |restriction| can only be a user restriction
- intent = mLocalService.createUserRestrictionSupportIntent(caller.getUserId(),
- restriction);
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ List<UserManager.EnforcingUser> sources = mUserManager
+ .getUserRestrictionSources(restriction, UserHandle.of(userId));
+ if (sources == null || sources.isEmpty()) {
+ // The restriction is not enforced.
+ return null;
+ } else if (sources.size() > 1) {
+ // In this case, we'll show an admin support dialog that does not
+ // specify the admin.
+ // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
+ // the admin for the calling user.
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ return result;
+ }
+ final UserManager.EnforcingUser enforcingUser = sources.get(0);
+ final int sourceType = enforcingUser.getUserRestrictionSource();
+ final int enforcingUserId = enforcingUser.getUserHandle().getIdentifier();
+ if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) {
+ // Restriction was enforced by PO
+ final ComponentName profileOwner = mOwners.getProfileOwnerComponent(
+ enforcingUserId);
+ if (profileOwner != null) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, enforcingUserId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ profileOwner);
+ return result;
+ }
+ } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
+ // Restriction was enforced by DO
+ final Pair<Integer, ComponentName> deviceOwner =
+ mOwners.getDeviceOwnerUserIdAndComponent();
+ if (deviceOwner != null) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, deviceOwner.first);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ deviceOwner.second);
+ return result;
+ }
+ } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) {
+ /*
+ * In this case, the user restriction is enforced by the system.
+ * So we won't show an admin support intent, even if it is also
+ * enforced by a profile/device owner.
+ */
+ return null;
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
- if (intent != null) {
+ return null;
+ }
+
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @return Details of admin and user which enforced the restriction for the userId.
+ */
+ @Override
+ public Bundle getEnforcingAdminAndUserDetails(int userId, String restriction) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+ return getEnforcingAdminAndUserDetailsInternal(userId, restriction);
+ }
+
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ */
+ @Override
+ public Intent createAdminSupportIntent(String restriction) {
+ Objects.requireNonNull(restriction);
+ final CallerIdentity caller = getCallerIdentity();
+ final int userId = caller.getUserId();
+ Intent intent = null;
+ if (getEnforcingAdminAndUserDetailsInternal(userId, restriction) != null) {
+ intent = createShowAdminSupportIntent(userId);
intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, restriction);
}
return intent;
@@ -14061,6 +14111,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
Preconditions.checkCallAuthorization(isManagedProfile(userHandle),
"You can not get organization name outside a managed profile, userId = %d",
userHandle);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index aca7cc9..e012dd2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -436,7 +436,6 @@
private static final String SYSPROP_START_UPTIME = "sys.system_server.start_uptime";
private Future<?> mZygotePreload;
- private Future<?> mBlobStoreServiceStart;
private final SystemServerDumper mDumper = new SystemServerDumper();
@@ -2251,12 +2250,9 @@
t.traceEnd();
}
- mBlobStoreServiceStart = SystemServerInitThreadPool.submit(() -> {
- final TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
- traceLog.traceBegin(START_BLOB_STORE_SERVICE);
- mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
- traceLog.traceEnd();
- }, START_BLOB_STORE_SERVICE);
+ t.traceBegin(START_BLOB_STORE_SERVICE);
+ mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
// Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
t.traceBegin("StartDreamManager");
@@ -2655,9 +2651,6 @@
mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
t.traceEnd();
- ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
- START_BLOB_STORE_SERVICE);
-
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
diff --git a/services/tests/PackageManager/packageinstaller/Android.bp b/services/tests/PackageManager/packageinstaller/Android.bp
new file mode 100644
index 0000000..35d754b
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "PackageInstallerTests",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "junit",
+ "kotlin-test",
+ "truth-prebuilt",
+ ],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/services/tests/PackageManager/packageinstaller/AndroidManifest.xml b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml
new file mode 100644
index 0000000..d706258
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.packageinstaller.test">
+
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.packageinstaller.test"
+ />
+
+</manifest>
+
diff --git a/services/tests/PackageManager/packageinstaller/AndroidTest.xml b/services/tests/PackageManager/packageinstaller/AndroidTest.xml
new file mode 100644
index 0000000..c39285ff
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<configuration description="Test module config for PackageInstallerTests">
+ <option name="test-tag" value="PackageInstallerTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="PackageInstallerTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.packageinstaller.test" />
+ </test>
+</configuration>
diff --git a/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt
new file mode 100644
index 0000000..d7d2726
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.packageinstaller.test
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+
+class ExportedComponentTest {
+
+ private val context: Context = InstrumentationRegistry.getContext()
+
+ @Test
+ fun verifyNoExportedReceivers() {
+ val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
+ data = Uri.parse("content://mockForTest")
+ }
+ val packageInstallers = context.packageManager.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY or PackageManager.MATCH_DISABLED_COMPONENTS)
+ .map { it.activityInfo.packageName }
+ .distinct()
+ .map { context.packageManager.getPackageInfo(it, PackageManager.GET_RECEIVERS) }
+
+ assertThat(packageInstallers).isNotEmpty()
+
+ packageInstallers.forEach {
+ val exported = it.receivers.filter { it.exported }
+ assertWithMessage("Receivers should not be exported").that(exported).isEmpty()
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 17a5dcc..3cab5ec 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.MANAGE_APPOPS"/>
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml
new file mode 100644
index 0000000..a4de08a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="true" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml
new file mode 100644
index 0000000..47649d7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml
new file mode 100644
index 0000000..4fd9ebf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="false" />
+ <individual-sensor-privacy sensor="2" enabled="true" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml
new file mode 100644
index 0000000..e8f9edf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="false" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index 022fadc..609768c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -21,12 +21,16 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import android.app.ActivityManager;
+import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
+import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -48,8 +52,14 @@
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
-import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -60,8 +70,11 @@
* Build/Install/Run:
* atest FrameworksMockingServicesTests:CacheOomRankerTest
*/
+@SuppressWarnings("GuardedBy") // No tests are concurrent, so no need to test locking.
@RunWith(MockitoJUnitRunner.class)
public class CacheOomRankerTest {
+ private static final Instant NOW = LocalDate.of(2021, 1, 1).atStartOfDay(
+ ZoneOffset.UTC).toInstant();
@Mock
private AppOpsService mAppOpsService;
@@ -82,6 +95,7 @@
private int mNextUid = 30000;
private int mNextPackageUid = 40000;
private int mNextPackageName = 1;
+ private Map<Integer, Long> mPidToRss;
private TestExecutor mExecutor = new TestExecutor();
private CacheOomRanker mCacheOomRanker;
@@ -107,7 +121,15 @@
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
- mCacheOomRanker = new CacheOomRanker(mAms);
+ mPidToRss = new HashMap<>();
+ mCacheOomRanker = new CacheOomRanker(
+ mAms,
+ pid -> {
+ Long rss = mPidToRss.get(pid);
+ assertThat(rss).isNotNull();
+ return new long[]{rss};
+ }
+ );
mCacheOomRanker.init(mExecutor);
}
@@ -136,6 +158,15 @@
mExecutor.init();
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS,
+ Integer.toString(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1),
+ false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.mPreserveTopNApps)
+ .isEqualTo(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1);
+
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f),
false);
@@ -165,6 +196,9 @@
@Test
public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 5,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 0.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */1.0f);
@@ -172,36 +206,40 @@
ProcessList list = new ProcessList();
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(lastUsed40MinutesAgo);
ProcessRecord lastUsed42MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(lastUsed42MinutesAgo);
ProcessRecord lastUsed60MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(60).toMillis(), 1024L, 10000);
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
processList.add(lastUsed60MinutesAgo);
ProcessRecord lastUsed15MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(lastUsed15MinutesAgo);
ProcessRecord lastUsed17MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20);
processList.add(lastUsed17MinutesAgo);
// Only re-ranking 5 entries so this should stay in most recent position.
ProcessRecord lastUsed30MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20);
processList.add(lastUsed30MinutesAgo);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 5 ordered by least recently used first, then last processes position unchanged.
assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo,
lastUsed40MinutesAgo, lastUsed17MinutesAgo, lastUsed15MinutesAgo,
- lastUsed30MinutesAgo);
+ lastUsed30MinutesAgo).inOrder();
}
@Test
public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 0.0f,
/* pssWeight= */ 1.0f,
/* lruWeight= */ 0.0f);
@@ -209,145 +247,459 @@
ProcessList list = new ProcessList();
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(rss10k);
ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(rss20k);
ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(60).toMillis(), 1024L, 10000);
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
processList.add(rss1k);
ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(rss100k);
ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
processList.add(rss2k);
ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
processList.add(rss15k);
// Only re-ranking 6 entries so this should stay in most recent position.
ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 6 ordered by largest pss, then last processes position unchanged.
assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
- rss16k);
+ rss16k).inOrder();
}
@Test
+ public void reRankLruCachedApps_rssImpactsOrdering_cachedRssValues()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 10000000,
+ /* usesWeight= */ 0.0f,
+ /* pssWeight= */ 1.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(rss10k);
+ ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(rss20k);
+ ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
+ processList.add(rss1k);
+ ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(rss100k);
+ ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(rss2k);
+ ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
+ processList.add(rss15k);
+ // Only re-ranking 6 entries so this should stay in most recent position.
+ ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
+ processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // First 6 ordered by largest pss, then last processes position unchanged.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+
+ // Clear mPidToRss so that Process.getRss calls fail.
+ mPidToRss.clear();
+ // Mix up the process list to ensure that CacheOomRanker actually re-ranks.
+ Collections.swap(processList, 0, 1);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // Re ranking is the same.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_rssImpactsOrdering_profileRss()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ false,
+ /* rssUpdateRateMs= */ 10000000,
+ /* usesWeight= */ 0.0f,
+ /* pssWeight= */ 1.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 0L, 1000);
+ rss10k.mProfile.setLastRss(10 * 1024L);
+ processList.add(rss10k);
+ ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 0L, 2000);
+ rss20k.mProfile.setLastRss(20 * 1024L);
+ processList.add(rss20k);
+ ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10000);
+ rss1k.mProfile.setLastRss(1024L);
+ processList.add(rss1k);
+ ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10);
+ rss100k.mProfile.setLastRss(100 * 1024L);
+ processList.add(rss100k);
+ ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 0L, 20);
+ rss2k.mProfile.setLastRss(2 * 1024L);
+ processList.add(rss2k);
+ ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
+ rss15k.mProfile.setLastRss(15 * 1024L);
+ processList.add(rss15k);
+ // Only re-ranking 6 entries so this should stay in most recent position.
+ ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
+ rss16k.mProfile.setLastRss(16 * 1024L);
+ processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ // This should not be used, as RSS values are taken from mProfile.
+ mPidToRss.clear();
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // First 6 ordered by largest pss, then last processes position unchanged.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+
+ // Clear mPidToRss so that Process.getRss calls fail.
+ mPidToRss.clear();
+ // Mix up the process list to ensure that CacheOomRanker actually re-ranks.
+ Collections.swap(processList, 0, 1);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // Re ranking is the same.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+ }
+
+
+ @Test
public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 4,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 1.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- list.setLruProcessServiceStartLSP(1);
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(used1000);
ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(used2000);
ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(used10);
ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
processList.add(used20);
// Only re-ranking 6 entries so last two should stay in most recent position.
ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
processList.add(used500);
ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 4 ordered by uses, then last processes position unchanged.
assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500,
- used200);
+ used200).inOrder();
}
@Test
- public void reRankLruCachedApps_notEnoughProcesses() throws InterruptedException {
+ public void reRankLruCachedApps_fewProcesses() throws InterruptedException {
setConfig(/* numberToReRank= */ 4,
- /* usesWeight= */ 0.5f,
- /* pssWeight= */ 0.2f,
- /* lruWeight= */ 0.3f);
-
- ProcessList list = new ProcessList();
- ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
- ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
- processList.add(unknownAdj1);
- ProcessRecord unknownAdj2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
- processList.add(unknownAdj2);
- ProcessRecord unknownAdj3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
- processList.add(unknownAdj3);
- ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
- processList.add(foregroundAdj);
- ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
- processList.add(serviceAdj);
- ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
- processList.add(systemAdj);
-
- // 6 Processes but only 3 in eligible for cache so no re-ranking.
- mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
-
- // All positions unchanged.
- assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3,
- foregroundAdj, serviceAdj, systemAdj);
- }
-
- @Test
- public void reRankLruCachedApps_notEnoughNonServiceProcesses() throws InterruptedException {
- setConfig(/* numberToReRank= */ 4,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 1.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- list.setLruProcessServiceStartLSP(4);
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(used1000);
ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(used2000);
ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(used10);
- ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
- processList.add(used20);
- ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
- processList.add(used500);
- ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
- processList.add(used200);
+ ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(foregroundAdj);
+ ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(serviceAdj);
+ ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(systemAdj);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
- // All positions unchanged.
- assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
- used200);
+ // 6 processes, only 3 in eligible for cache, so only those are re-ranked.
+ assertThat(processList).containsExactly(used10, used1000, used2000,
+ foregroundAdj, serviceAdj, systemAdj).inOrder();
}
- private void setConfig(int numberToReRank, float useWeight, float pssWeight, float lruWeight)
+ @Test
+ public void reRankLruCachedApps_fewNonServiceProcesses() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 4,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ ProcessRecord service1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(service1);
+ ProcessRecord service2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(service2);
+ ProcessRecord service3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(service3);
+ list.setLruProcessServiceStartLSP(3);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // Services unchanged, rest re-ranked.
+ assertThat(processList).containsExactly(used10, used1000, used2000, service1, service2,
+ service3).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_manyProcessesThenFew() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList set1List = new ProcessList();
+ ArrayList<ProcessRecord> set1ProcessList = set1List.getLruProcessesLSP();
+ ProcessRecord set1Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ set1ProcessList.add(set1Used1000);
+ ProcessRecord set1Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ set1ProcessList.add(set1Used2000);
+ ProcessRecord set1Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ set1ProcessList.add(set1Used10);
+ ProcessRecord set1Uses20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ set1ProcessList.add(set1Uses20);
+ ProcessRecord set1Uses500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ set1ProcessList.add(set1Uses500);
+ ProcessRecord set1Uses200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ set1ProcessList.add(set1Uses200);
+ set1List.setLruProcessServiceStartLSP(set1ProcessList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(set1ProcessList,
+ set1List.getLruProcessServiceStartLOSP());
+ assertThat(set1ProcessList).containsExactly(set1Used10, set1Uses20, set1Uses200,
+ set1Uses500, set1Used1000, set1Used2000).inOrder();
+
+ ProcessList set2List = new ProcessList();
+ ArrayList<ProcessRecord> set2ProcessList = set2List.getLruProcessesLSP();
+ ProcessRecord set2Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ set2ProcessList.add(set2Used1000);
+ ProcessRecord set2Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ set2ProcessList.add(set2Used2000);
+ ProcessRecord set2Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ set2ProcessList.add(set2Used10);
+ ProcessRecord set2ForegroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ set2ProcessList.add(set2ForegroundAdj);
+ ProcessRecord set2ServiceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ set2ProcessList.add(set2ServiceAdj);
+ ProcessRecord set2SystemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ set2ProcessList.add(set2SystemAdj);
+ set2List.setLruProcessServiceStartLSP(set2ProcessList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(set2ProcessList,
+ set2List.getLruProcessServiceStartLOSP());
+ assertThat(set2ProcessList).containsExactly(set2Used10, set2Used1000, set2Used2000,
+ set2ForegroundAdj, set2ServiceAdj, set2SystemAdj).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 3,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ // Preserving the top 3 processes, so these should not be re-ranked.
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // First 3 ordered by uses, then last processes position unchanged.
+ assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500,
+ used200).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps_allAppsUnchanged()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 100,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // Nothing reordered, as we preserve the top 100 apps.
+ assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
+ used200).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps_negativeReplacedWithDefault()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ -100,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ // Negative preserveTopNApps interpreted as the default (3), so the last three are unranked.
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // First 3 apps re-ranked, as preserveTopNApps is interpreted as 3.
+ assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500,
+ used200).inOrder();
+ }
+
+ private void setConfig(int numberToReRank, int preserveTopNApps, boolean useFrequentRss,
+ long rssUpdateRateMs, float usesWeight, float pssWeight, float lruWeight)
throws InterruptedException {
mExecutor.init(4);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -355,6 +707,18 @@
Integer.toString(numberToReRank),
false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS,
+ Integer.toString(preserveTopNApps),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_USE_FREQUENT_RSS,
+ Boolean.toString(useFrequentRss),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS,
+ Long.toString(rssUpdateRateMs),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
Float.toString(lruWeight),
false);
@@ -364,17 +728,19 @@
false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT,
- Float.toString(useWeight),
+ Float.toString(usesWeight),
false);
mExecutor.waitForLatch();
assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank);
+ assertThat(mCacheOomRanker.mUseFrequentRss).isEqualTo(useFrequentRss);
+ assertThat(mCacheOomRanker.mRssUpdateRateMs).isEqualTo(rssUpdateRateMs);
assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight);
- assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(useWeight);
+ assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(usesWeight);
assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight);
}
private ProcessRecord nextProcessRecord(int setAdj, long lastActivityTime, long lastRss,
- int returnedToCacheCount) {
+ int wentToForegroundCount) {
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = "a.package.name" + mNextPackageName++;
ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++);
@@ -382,14 +748,20 @@
app.info.uid = mNextPackageUid++;
// Exact value does not mater, it can be any state for which compaction is allowed.
app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- app.mState.setSetAdj(setAdj);
+ app.mState.setCurAdj(setAdj);
app.setLastActivityTime(lastActivityTime);
- app.mProfile.setLastRss(lastRss);
+ mPidToRss.put(app.getPid(), lastRss);
app.mState.setCached(false);
- for (int i = 0; i < returnedToCacheCount; ++i) {
- app.mState.setCached(false);
- app.mState.setCached(true);
+ for (int i = 0; i < wentToForegroundCount; ++i) {
+ app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ app.mState.setSetProcState(ActivityManager.PROCESS_STATE_CACHED_RECENT);
}
+ // Sets the thread returned by ProcessRecord#getThread, which we use to check whether the
+ // app is currently launching.
+ ProcessStatsService processStatsService = new ProcessStatsService(
+ mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(),
+ "procstats"));
+ app.makeActive(mock(IApplicationThread.class), processStatsService);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 4d86c87..85ef8f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -182,7 +182,14 @@
}
private void mockDeviceConfigPerformance() {
- String configString = "mode=2,downscaleFactor=0.5";
+ String configString = "mode=2,downscaleFactor=0.5,useAngle=false";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configString);
+ }
+
+ // ANGLE will be disabled for most apps, so treat enabling ANGLE as a special case.
+ private void mockDeviceConfigPerformanceEnableAngle() {
+ String configString = "mode=2,downscaleFactor=0.5,useAngle=true";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
}
@@ -200,7 +207,7 @@
}
private void mockDeviceConfigInvalid() {
- String configString = "mode=2,downscaleFactor=0.55";
+ String configString = "";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
}
@@ -212,7 +219,8 @@
}
private void mockGameModeOptInAll() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -224,7 +232,8 @@
}
private void mockGameModeOptInPerformance() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -234,7 +243,8 @@
}
private void mockGameModeOptInBattery() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
@@ -244,7 +254,8 @@
}
private void mockInterventionAllowDownscaleTrue() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, true);
@@ -254,7 +265,8 @@
}
private void mockInterventionAllowDownscaleFalse() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false);
@@ -263,6 +275,27 @@
.thenReturn(applicationInfo);
}
+ private void mockInterventionAllowAngleTrue() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ metaDataBundle.putBoolean(
+ GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, true);
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ }
+
+ private void mockInterventionAllowAngleFalse() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ metaDataBundle.putBoolean(
+ GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, false);
+ applicationInfo.metaData = metaDataBundle;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ }
+
/**
* By default game mode is not supported.
*/
@@ -340,11 +373,12 @@
*/
@Test
public void testSetGameModePermissionDenied() {
+ mockModifyGameModeGranted();
+ mockDeviceConfigAll();
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
// Update the game mode so we can read back something valid.
- mockModifyGameModeGranted();
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
@@ -427,6 +461,19 @@
assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling);
}
+ private void checkAngleEnabled(GameManagerService gameManagerService, int gameMode,
+ boolean angleEnabled) {
+ gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+
+ // Validate GamePackageConfiguration returns the correct value.
+ GameManagerService.GamePackageConfiguration config =
+ gameManagerService.getConfig(mPackageName);
+ assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
+
+ // Validate GameManagerService.getAngleEnabled() returns the correct value.
+ assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
+ }
+
/**
* Phenotype device config exists, but is only propagating the default value.
*/
@@ -592,6 +639,50 @@
}
/**
+ * PERFORMANCE game mode is configured through Phenotype. The app hasn't specified any metadata.
+ */
+ @Test
+ public void testInterventionAllowAngleDefault() throws Exception {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockDeviceConfigPerformance();
+ mockModifyGameModeGranted();
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
+ }
+
+ /**
+ * PERFORMANCE game mode is configured through Phenotype. The app has opted-out of ANGLE.
+ */
+ @Test
+ public void testInterventionAllowAngleFalse() throws Exception {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockDeviceConfigPerformanceEnableAngle();
+ mockInterventionAllowAngleFalse();
+ mockModifyGameModeGranted();
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
+ }
+
+ /**
+ * PERFORMANCE game mode is configured through Phenotype. The app has redundantly specified
+ * the ANGLE metadata default value of "true".
+ */
+ @Test
+ public void testInterventionAllowAngleTrue() throws Exception {
+ mockDeviceConfigPerformanceEnableAngle();
+ mockInterventionAllowAngleTrue();
+
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockModifyGameModeGranted();
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, true);
+ }
+
+ /**
* PERFORMANCE game mode is configured through Phenotype, but the app has also opted into the
* same mode. No interventions for this game mode should be available in this case.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index f703e2e..d0b2eda 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -906,6 +906,21 @@
}
@Test
+ public void testProviderRequest_DelayedRequest_Remove() {
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = new LocationRequest.Builder(60000)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+ mManager.unregisterLocationRequest(listener1);
+
+ mInjector.getAlarmHelper().incrementAlarmTime(60000);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ }
+
+ @Test
public void testProviderRequest_SpamRequesting() {
mProvider.setProviderLocation(createLocation(NAME, mRandom));
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 63996f0..4d6f49e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -90,7 +90,7 @@
}
@Test
- public void testThrottle() {
+ public void testThrottle_stationaryExit() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
mProvider.getController().setRequest(request);
@@ -113,6 +113,29 @@
}
@Test
+ public void testThrottle_idleExit() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+ verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+ verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceIdleHelper().setIdle(false);
+ verify(mDelegate, times(2)).onSetRequest(request);
+ verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+ }
+
+ @Test
public void testThrottle_NoInitialLocation() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
index 7a6110b..f17fa62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
@@ -79,8 +79,8 @@
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
@@ -97,8 +97,8 @@
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
@@ -118,8 +118,8 @@
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, null)
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
@@ -133,6 +133,22 @@
}
}
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendModifiedForUser() {
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+ verify(pms).sendPackageBroadcast(
+ eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+
+ var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(modifiedUids).asList().containsExactly(
+ packageSetting1.appId, packageSetting2.appId)
+ }
+
private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
this.put(TEST_USER_ID, uids)
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
new file mode 100644
index 0000000..f94377f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2020 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.power;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_VR;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import static com.android.server.power.ScreenUndimDetector.DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
+import static com.android.server.power.ScreenUndimDetector.KEY_KEEP_SCREEN_ON_ENABLED;
+import static com.android.server.power.ScreenUndimDetector.KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
+import static com.android.server.power.ScreenUndimDetector.KEY_UNDIMS_REQUIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.server.power.ScreenUndimDetector}
+ */
+@RunWith(JUnit4.class)
+public class ScreenUndimDetectorTest {
+ private static final List<Integer> ALL_POLICIES =
+ Arrays.asList(POLICY_OFF,
+ POLICY_DOZE,
+ POLICY_DIM,
+ POLICY_BRIGHT,
+ POLICY_VR);
+
+ @ClassRule
+ public static final TestableContext sContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ private ScreenUndimDetector mScreenUndimDetector;
+
+ private final TestClock mClock = new TestClock();
+
+ private static class TestClock extends ScreenUndimDetector.InternalClock {
+ long mCurrentTime = 0;
+ @Override
+ public long getCurrentTime() {
+ return mCurrentTime;
+ }
+
+ public void advanceTime(long millisAdvanced) {
+ mCurrentTime += millisAdvanced;
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(1), false /*makeDefault*/);
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
+ Long.toString(DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS),
+ false /*makeDefault*/);
+
+ mScreenUndimDetector = new ScreenUndimDetector(mClock);
+ mScreenUndimDetector.systemReady(sContext);
+ }
+
+ @Test
+ public void recordScreenPolicy_disabledByFlag_noop() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/);
+
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+
+ @Test
+ public void recordScreenPolicy_samePolicy_noop() {
+ for (int policy : ALL_POLICIES) {
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(policy);
+ mScreenUndimDetector.recordScreenPolicy(policy);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_extends() {
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isTrue();
+ }
+
+ @Test
+ public void recordScreenPolicy_otherTransitions_doesNotExtend() {
+ for (int from : ALL_POLICIES) {
+ for (int to : ALL_POLICIES) {
+ if (from == POLICY_DIM && to == POLICY_BRIGHT) {
+ continue;
+ }
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(from);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_twoUndimsNeeded_extends() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isTrue();
+ }
+
+ @Test
+ public void recordScreenPolicy_dimBrightDimOff_resetsCounter_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+
+ @Test
+ public void recordScreenPolicy_undimToOff_resetsCounter() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+
+ @Test
+ public void recordScreenPolicy_undimOffUndim_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ // undim
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ // off
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+ // second undim
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(1);
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_tooFarApart_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ mClock.advanceTime(DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS + 5);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(1);
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToNonBright_resets() {
+ for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+ setup();
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis = 123;
+ mScreenUndimDetector.mWakeLock.acquire();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+
+ }
+
+ @Test
+ public void recordScreenPolicy_brightToNonDim_resets() {
+ for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+ setup();
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis = 123;
+ mScreenUndimDetector.mWakeLock.acquire();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_otherTransitions_doesNotReset() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(3),
+ false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ for (int from : ALL_POLICIES) {
+ for (int to : ALL_POLICIES) {
+ if (from == POLICY_DIM && to != POLICY_BRIGHT) {
+ continue;
+ }
+ if (from == POLICY_BRIGHT && to != POLICY_DIM) {
+ continue;
+ }
+ mScreenUndimDetector.mCurrentScreenPolicy = POLICY_OFF;
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis =
+ SystemClock.currentThreadTimeMillis();
+
+ mScreenUndimDetector.recordScreenPolicy(from);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isNotEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isNotEqualTo(0);
+ }
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
index ba79a76..38f01b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -16,12 +16,18 @@
package com.android.server.sensorprivacy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
+import android.app.AppOpsManagerInternal;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Environment;
@@ -33,8 +39,10 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
import com.android.server.SensorPrivacyService;
+import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -44,6 +52,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.concurrent.CompletableFuture;
@RunWith(AndroidTestingRunner.class)
public class SensorPrivacyServiceMockingTest {
@@ -63,10 +72,21 @@
public static final String PERSISTENCE_FILE6 =
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
+ public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
+ public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml";
+ public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml";
+ public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
+
private Context mContext;
@Mock
private AppOpsManager mMockedAppOpsManager;
@Mock
+ private AppOpsManagerInternal mMockedAppOpsManagerInternal;
+ @Mock
private UserManagerInternal mMockedUserManagerInternal;
@Mock
private ActivityManager mMockedActivityManager;
@@ -134,13 +154,103 @@
}
}
+ @Test
+ public void testServiceInit_AppOpsRestricted_micMute_camMute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE, true, true);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micMute_camUnmute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE, true, false);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micUnmute_camMute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE, false, true);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micUnmute_camUnmute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE, false, false);
+ }
+
+ private void testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute,
+ boolean expectedMicState, boolean expectedCamState)
+ throws IOException {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(LocalServices.class)
+ .spyStatic(Environment.class)
+ .startMocking();
+
+ try {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ spyOn(mContext);
+
+ doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+ doReturn(mMockedAppOpsManagerInternal)
+ .when(() -> LocalServices.getService(AppOpsManagerInternal.class));
+ doReturn(mMockedUserManagerInternal)
+ .when(() -> LocalServices.getService(UserManagerInternal.class));
+ doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
+ doReturn(mMockedActivityTaskManager)
+ .when(mContext).getSystemService(ActivityTaskManager.class);
+ doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
+ TelephonyManager.class);
+
+ String dataDir = mContext.getApplicationInfo().dataDir;
+ doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
+
+ File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
+ onDeviceFile.delete();
+
+ doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
+ doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+ .getUserInfo(0);
+
+ CompletableFuture<Boolean> micState = new CompletableFuture<>();
+ CompletableFuture<Boolean> camState = new CompletableFuture<>();
+ doAnswer(invocation -> {
+ int code = invocation.getArgument(0);
+ boolean restricted = invocation.getArgument(1);
+ if (code == AppOpsManager.OP_RECORD_AUDIO) {
+ micState.complete(restricted);
+ } else if (code == AppOpsManager.OP_CAMERA) {
+ camState.complete(restricted);
+ }
+ return null;
+ }).when(mMockedAppOpsManagerInternal).setGlobalRestriction(anyInt(), anyBoolean(),
+ any());
+
+ initServiceWithPersistenceFile(onDeviceFile, persistenceFileMicMuteCamMute, 0);
+
+ Assert.assertTrue(micState.join() == expectedMicState);
+ Assert.assertTrue(camState.join() == expectedCamState);
+
+ } finally {
+ mockitoSession.finishMocking();
+ }
+ }
+
private void initServiceWithPersistenceFile(File onDeviceFile,
String persistenceFilePath) throws IOException {
+ initServiceWithPersistenceFile(onDeviceFile, persistenceFilePath, -1);
+ }
+
+ private void initServiceWithPersistenceFile(File onDeviceFile,
+ String persistenceFilePath, int startingUserId) throws IOException {
if (persistenceFilePath != null) {
Files.copy(mContext.getAssets().open(persistenceFilePath),
onDeviceFile.toPath());
}
- new SensorPrivacyService(mContext);
+ SensorPrivacyService service = new SensorPrivacyService(mContext);
+ if (startingUserId != -1) {
+ SystemService.TargetUser mockedTargetUser =
+ ExtendedMockito.mock(SystemService.TargetUser.class);
+ doReturn(startingUserId).when(mockedTargetUser).getUserIdentifier();
+ service.onUserStarting(mockedTargetUser);
+ }
onDeviceFile.delete();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 53483f6..a49afc7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -18,13 +18,18 @@
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.os.FileObserver.CLOSE_WRITE;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_CROP;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
@@ -34,6 +39,7 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
@@ -41,6 +47,7 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Context;
@@ -49,8 +56,10 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
+import android.graphics.Color;
import android.hardware.display.DisplayManager;
-import android.os.UserHandle;
+import android.os.RemoteException;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
@@ -143,7 +152,6 @@
sContext.getTestablePermissions().setPermission(
android.Manifest.permission.SET_WALLPAPER,
PackageManager.PERMISSION_GRANTED);
- doNothing().when(sContext).sendBroadcastAsUser(any(), any());
//Wallpaper components
sWallpaperService = mock(IWallpaperConnection.Stub.class);
@@ -180,6 +188,7 @@
MockitoAnnotations.initMocks(this);
sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+ doNothing().when(sContext).sendBroadcastAsUser(any(), any());
final Display mockDisplay = mock(Display.class);
doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
@@ -242,13 +251,13 @@
*/
@Test
public void testDataCorrectAfterBoot() {
- mService.switchUser(UserHandle.USER_SYSTEM, null);
+ mService.switchUser(USER_SYSTEM, null);
final WallpaperData fallbackData = mService.mFallbackWallpaper;
assertEquals("Fallback wallpaper component should be ImageWallpaper.",
sImageWallpaperComponentName, fallbackData.wallpaperComponent);
- verifyLastWallpaperData(UserHandle.USER_SYSTEM, sDefaultWallpaperComponent);
+ verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent);
verifyDisplayData();
}
@@ -261,7 +270,7 @@
assumeThat(sDefaultWallpaperComponent,
not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
- final int testUserId = UserHandle.USER_SYSTEM;
+ final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
@@ -281,7 +290,7 @@
*/
@Test
public void testSetCurrentComponent() throws Exception {
- final int testUserId = UserHandle.USER_SYSTEM;
+ final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
@@ -387,6 +396,42 @@
assertEquals(systemWallpaperData.primaryColors, shouldMatchSystem.primaryColors);
}
+ @Test
+ public void testWallpaperManagerCallbackInRightOrder() throws RemoteException {
+ WallpaperData wallpaper = new WallpaperData(
+ USER_SYSTEM, mService.getWallpaperDir(USER_SYSTEM), WALLPAPER, WALLPAPER_CROP);
+ wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ spyOn(wallpaper);
+ doReturn(wallpaper).when(mService).getWallpaperSafeLocked(wallpaper.userId, FLAG_SYSTEM);
+ doNothing().when(mService).switchWallpaper(any(), any());
+ doReturn(true).when(mService)
+ .bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
+ doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
+ doNothing().when(mService).generateCrop(wallpaper);
+
+ // timestamps of {ACTION_WALLPAPER_CHANGED, onWallpaperColorsChanged}
+ final long[] timestamps = new long[2];
+ doAnswer(invocation -> timestamps[0] = SystemClock.elapsedRealtime())
+ .when(sContext).sendBroadcastAsUser(any(), any());
+ doAnswer(invocation -> timestamps[1] = SystemClock.elapsedRealtime())
+ .when(mService).notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
+
+ assertNull(wallpaper.wallpaperObserver);
+ mService.switchUser(wallpaper.userId, null);
+ assertNotNull(wallpaper.wallpaperObserver);
+ // We will call onEvent directly, so stop watching the file.
+ wallpaper.wallpaperObserver.stopWatching();
+
+ spyOn(wallpaper.wallpaperObserver);
+ doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false);
+ wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER);
+
+ // ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged.
+ assertTrue(timestamps[1] > timestamps[0]);
+ }
+
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
private void verifyNoConnectionBeforeLastUser(int lastUserId) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 2892bf5..b3f7587 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -74,6 +74,7 @@
public class AuthSessionTest {
private static final String TEST_PACKAGE = "test_package";
+ private static final long TEST_REQUEST_ID = 22;
@Mock private Context mContext;
@Mock private ITrustManager mTrustManager;
@@ -112,6 +113,7 @@
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
0 /* operationId */,
0 /* userId */);
@@ -133,6 +135,7 @@
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
operationId,
userId);
assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size());
@@ -153,6 +156,7 @@
eq(userId),
eq(mSensorReceiver),
eq(TEST_PACKAGE),
+ eq(TEST_REQUEST_ID),
eq(sensor.getCookie()),
anyBoolean() /* allowBackgroundAuthentication */);
}
@@ -185,6 +189,33 @@
}
@Test
+ public void testCancelReducesAppetiteForCookies() throws Exception {
+ setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
+ setupFingerprint(1 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 44 /* operationId */,
+ 2 /* userId */);
+
+ session.goToInitialState();
+
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+ }
+
+ session.onCancelAuthSession(false /* force */);
+
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ session.onCookieReceived(sensor.getCookie());
+ assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+ }
+ }
+
+ @Test
public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
@@ -212,6 +243,7 @@
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
operationId,
userId);
assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size());
@@ -238,7 +270,7 @@
// fingerprint sensor does not start even if all cookies are received
assertEquals(STATE_AUTH_STARTED, session.getState());
verify(mStatusBarService).showAuthenticationDialog(any(), any(), any(),
- anyBoolean(), anyBoolean(), anyInt(), any(), anyLong(), anyInt());
+ anyBoolean(), anyBoolean(), anyInt(), anyLong(), any(), anyLong(), anyInt());
// Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
session.onDialogAnimatedIn();
@@ -277,6 +309,7 @@
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
0 /* operationId */,
0 /* userId */);
@@ -285,7 +318,8 @@
sessionConsumer.accept(session);
- verify(faceAuthenticator).cancelAuthenticationFromService(eq(mToken), eq(TEST_PACKAGE));
+ verify(faceAuthenticator).cancelAuthenticationFromService(
+ eq(mToken), eq(TEST_PACKAGE), eq(TEST_REQUEST_ID));
}
private PreAuthInfo createPreAuthInfo(List<BiometricSensor> sensors, int userId,
@@ -302,14 +336,14 @@
private AuthSession createAuthSession(List<BiometricSensor> sensors,
boolean checkDevicePolicyManager, @Authenticators.Types int authenticators,
- long operationId, int userId) throws RemoteException {
+ long requestId, long operationId, int userId) throws RemoteException {
final PromptInfo promptInfo = createPromptInfo(authenticators);
final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, promptInfo,
checkDevicePolicyManager);
return new AuthSession(mContext, mStatusBarService, mSysuiReceiver, mKeyStore,
- mRandom, mClientDeathReceiver, preAuthInfo, mToken, operationId, userId,
+ mRandom, mClientDeathReceiver, preAuthInfo, mToken, requestId, operationId, userId,
mSensorReceiver, mClientReceiver, TEST_PACKAGE, promptInfo,
false /* debugEnabled */, mFingerprintSensorProps);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 7c7afb7..69d8e89 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -85,6 +85,7 @@
import org.mockito.MockitoAnnotations;
import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
@Presubmit
@SmallTest
@@ -93,6 +94,7 @@
private static final String TAG = "BiometricServiceTest";
private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final long TEST_REQUEST_ID = 44;
private static final String ERROR_HW_UNAVAILABLE = "hw_unavailable";
private static final String ERROR_NOT_RECOGNIZED = "not_recognized";
@@ -151,6 +153,7 @@
.thenReturn(mock(BiometricStrengthController.class));
when(mInjector.getTrustManager()).thenReturn(mTrustManager);
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
+ when(mInjector.getRequestGenerator()).thenReturn(new AtomicLong(TEST_REQUEST_ID - 1));
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -215,8 +218,7 @@
mBiometricService.mCurrentAuthSession.getState());
verify(mBiometricService.mCurrentAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
- .cancelAuthenticationFromService(any(),
- any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
// Simulate ERROR_CANCELED received from HAL
mBiometricService.mBiometricSensorReceiver.onError(
@@ -272,8 +274,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -357,8 +360,9 @@
eq(false) /* credentialAllowed */,
eq(false) /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -467,6 +471,7 @@
anyInt() /* userId */,
any(IBiometricSensorReceiver.class),
anyString() /* opPackageName */,
+ eq(TEST_REQUEST_ID),
cookieCaptor.capture() /* cookie */,
anyBoolean() /* allowBackgroundAuthentication */);
@@ -488,8 +493,9 @@
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Hardware authenticated
@@ -543,8 +549,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -705,8 +712,9 @@
anyBoolean() /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
anyString(),
- anyLong() /* sessionId */,
+ anyLong() /* requestId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -805,8 +813,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -885,8 +894,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -1030,8 +1040,7 @@
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mSensors.get(0).impl).cancelAuthenticationFromService(
- any(),
- any());
+ any(), any(), anyLong());
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -1051,7 +1060,7 @@
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
}
@Test
@@ -1071,7 +1080,7 @@
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
}
@Test
@@ -1088,7 +1097,7 @@
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
@@ -1126,7 +1135,7 @@
false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
- TEST_PACKAGE_NAME);
+ TEST_PACKAGE_NAME, TEST_REQUEST_ID);
waitForIdle();
// Pretend that the HAL has responded to cancel with ERROR_CANCELED
@@ -1353,8 +1362,8 @@
int authenticators = Authenticators.BIOMETRIC_STRONG;
assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- authenticators);
+ long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
@@ -1366,7 +1375,7 @@
authenticators = Authenticators.BIOMETRIC_WEAK;
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
authenticators);
waitForIdle();
@@ -1377,8 +1386,9 @@
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Requesting strong and credential, when credential is setup
@@ -1387,7 +1397,7 @@
when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
authenticators);
waitForIdle();
@@ -1399,8 +1409,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Un-downgrading the authenticator allows successful strong auth
@@ -1414,7 +1425,7 @@
authenticators = Authenticators.BIOMETRIC_STRONG;
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -1424,8 +1435,9 @@
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -1617,11 +1629,12 @@
mBiometricService.mStatusBarService = mock(IStatusBarService.class);
}
- private void invokeAuthenticateAndStart(IBiometricService.Stub service,
+ private long invokeAuthenticateAndStart(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
Integer authenticators) throws Exception {
// Request auth, creates a pending session
- invokeAuthenticate(service, receiver, requireConfirmation, authenticators);
+ final long requestId = invokeAuthenticate(
+ service, receiver, requireConfirmation, authenticators);
waitForIdle();
startPendingAuthSession(mBiometricService);
@@ -1629,6 +1642,8 @@
assertNotNull(mBiometricService.mCurrentAuthSession);
assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+
+ return requestId;
}
private static void startPendingAuthSession(BiometricService service) throws Exception {
@@ -1644,10 +1659,10 @@
service.mImpl.onReadyForAuthentication(cookie);
}
- private static void invokeAuthenticate(IBiometricService.Stub service,
+ private static long invokeAuthenticate(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
Integer authenticators) throws Exception {
- service.authenticate(
+ return service.authenticate(
new Binder() /* token */,
0 /* operationId */,
0 /* userId */,
@@ -1657,9 +1672,9 @@
false /* checkDevicePolicy */));
}
- private static void invokeAuthenticateForWorkApp(IBiometricService.Stub service,
+ private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, Integer authenticators) throws Exception {
- service.authenticate(
+ return service.authenticate(
new Binder() /* token */,
0 /* operationId */,
0 /* userId */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index a41f79e..e3e3900 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -40,6 +40,7 @@
import android.testing.TestableContext;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -193,7 +194,7 @@
// Request it to be canceled. The operation can be canceled immediately, and the scheduler
// should go back to idle, since in this case the framework has not even requested the HAL
// to authenticate yet.
- mScheduler.cancelAuthenticationOrDetection(mToken);
+ mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
assertNull(mScheduler.mCurrentOperation);
}
@@ -303,7 +304,7 @@
mScheduler.mPendingOperations.getFirst().mState);
// Request cancel before the authentication client has started
- mScheduler.cancelAuthenticationOrDetection(mToken);
+ mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
waitForIdle();
assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
mScheduler.mPendingOperations.getFirst().mState);
@@ -318,6 +319,107 @@
}
@Test
+ public void testCancels_whenAuthRequestIdNotSet() {
+ testCancelsWhenRequestId(null /* requestId */, 2, true /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdNotSet_notStarted() {
+ testCancelsWhenRequestId(null /* requestId */, 2, false /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdMatches() {
+ testCancelsWhenRequestId(200L, 200, true /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdMatches_noStarted() {
+ testCancelsWhenRequestId(200L, 200, false /* started */);
+ }
+
+ @Test
+ public void testDoesNotCancel_whenAuthRequestIdMismatched() {
+ testCancelsWhenRequestId(10L, 20, true /* started */);
+ }
+
+ @Test
+ public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
+ testCancelsWhenRequestId(10L, 20, false /* started */);
+ }
+
+ private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
+ boolean started) {
+ final boolean matches = requestId == null || requestId == cancelRequestId;
+ final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ if (requestId != null) {
+ client.setRequestId(requestId);
+ }
+
+ mScheduler.scheduleClientMonitor(client);
+ if (started) {
+ mScheduler.startPreparedClient(client.getCookie());
+ }
+ waitForIdle();
+ mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
+ waitForIdle();
+
+ assertEquals(matches && started ? 1 : 0, client.mNumCancels);
+
+ if (matches) {
+ if (started) {
+ assertEquals(Operation.STATE_STARTED_CANCELING,
+ mScheduler.mCurrentOperation.mState);
+ }
+ } else {
+ if (started) {
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ } else {
+ assertEquals(Operation.STATE_WAITING_FOR_COOKIE,
+ mScheduler.mCurrentOperation.mState);
+ }
+ }
+ }
+
+ @Test
+ public void testCancelsPending_whenAuthRequestIdsSet() {
+ final long requestId1 = 10;
+ final long requestId2 = 20;
+ final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client1 = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ client1.setRequestId(requestId1);
+ final TestAuthenticationClient client2 = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ client2.setRequestId(requestId2);
+
+ mScheduler.scheduleClientMonitor(client1);
+ mScheduler.scheduleClientMonitor(client2);
+ mScheduler.startPreparedClient(client1.getCookie());
+ waitForIdle();
+ mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
+ waitForIdle();
+
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ assertEquals(Operation.STATE_WAITING_IN_QUEUE,
+ mScheduler.mPendingOperations.getFirst().mState);
+
+ mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
+ waitForIdle();
+
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
+ mScheduler.mPendingOperations.getFirst().mState);
+ }
+
+ @Test
public void testInterruptPrecedingClients_whenExpected() {
final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
withSettings().extraInterfaces(Interruptable.class));
@@ -377,12 +479,10 @@
@Override
protected void stopHalOperation() {
-
}
@Override
protected void startHalOperation() {
-
}
@Override
@@ -397,6 +497,7 @@
}
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+ int mNumCancels = 0;
public TestAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -428,6 +529,11 @@
public boolean wasUserDetected() {
return false;
}
+
+ public void cancel() {
+ mNumCancels++;
+ super.cancel();
+ }
}
private static class TestClientMonitor2 extends TestClientMonitor {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
new file mode 100644
index 0000000..09b5c5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class CompositeCallbackTest {
+
+ @Test
+ public void testNullCallback() {
+ BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
+ BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
+ BaseClientMonitor.Callback callback3 = null;
+
+ BaseClientMonitor.CompositeCallback callback = new BaseClientMonitor.CompositeCallback(
+ callback1, callback2, callback3);
+
+ BaseClientMonitor clientMonitor = mock(BaseClientMonitor.class);
+
+ callback.onClientStarted(clientMonitor);
+ verify(callback1).onClientStarted(eq(clientMonitor));
+ verify(callback2).onClientStarted(eq(clientMonitor));
+
+ callback.onClientFinished(clientMonitor, true /* success */);
+ verify(callback1).onClientFinished(eq(clientMonitor), eq(true));
+ verify(callback2).onClientFinished(eq(clientMonitor), eq(true));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
new file mode 100644
index 0000000..38e8dfa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors.fingerprint;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.fingerprint.FingerprintStateListener;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.EnrollClient;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class FingerprintStateCallbackTest {
+
+ private FingerprintStateCallback mCallback;
+
+ @Mock
+ FingerprintStateListener mFingerprintStateListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mCallback = new FingerprintStateCallback();
+ mCallback.registerFingerprintStateListener(mFingerprintStateListener);
+ }
+
+ @Test
+ public void testNoEnrollmentsToEnrollments_callbackNotified() {
+ testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
+ true /* expectCallback */, true /* expectedCallbackValue */);
+ }
+
+ @Test
+ public void testEnrollmentsToNoEnrollments_callbackNotified() {
+ testEnrollmentCallback(true /* changed */, false /* isNowEnrolled */,
+ true /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
+ @Test
+ public void testEnrollmentsToEnrollments_callbackNotNotified() {
+ testEnrollmentCallback(false /* changed */, true /* isNowEnrolled */,
+ false /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
+ private void testEnrollmentCallback(boolean changed, boolean isNowEnrolled,
+ boolean expectCallback, boolean expectedCallbackValue) {
+ EnrollClient<?> client = mock(EnrollClient.class);
+
+ final int userId = 10;
+ final int sensorId = 100;
+
+ when(client.hasEnrollmentStateChanged()).thenReturn(changed);
+ when(client.hasEnrollments()).thenReturn(isNowEnrolled);
+ when(client.getTargetUserId()).thenReturn(userId);
+ when(client.getSensorId()).thenReturn(sensorId);
+
+ mCallback.onClientFinished(client, true /* success */);
+ if (expectCallback) {
+ verify(mFingerprintStateListener).onEnrollmentsChanged(eq(userId), eq(sensorId),
+ eq(expectedCallbackValue));
+ } else {
+ verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+ anyBoolean());
+ }
+ }
+
+ @Test
+ public void testAuthentication_enrollmentCallbackNeverNotified() {
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ mCallback.onClientFinished(client, true /* success */);
+ verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+ anyBoolean());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 35c37ef..b51918e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -37,6 +37,7 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
@@ -58,6 +59,8 @@
private UserManager mUserManager;
@Mock
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @Mock
+ private FingerprintStateCallback mFingerprintStateCallback;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -87,8 +90,8 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ mFingerprintProvider = new TestableFingerprintProvider(mContext, mFingerprintStateCallback,
+ mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
@SuppressWarnings("rawtypes")
@@ -133,11 +136,12 @@
private static class TestableFingerprintProvider extends FingerprintProvider {
public TestableFingerprintProvider(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- super(context, props, halInstanceName, lockoutResetDispatcher,
+ super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
gestureAvailabilityDispatcher);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index 0a0dcc9..f6b9209 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -41,6 +41,7 @@
import com.android.internal.R;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import org.junit.Before;
import org.junit.Test;
@@ -67,6 +68,8 @@
Fingerprint21.HalResultController mHalResultController;
@Mock
private BiometricScheduler mScheduler;
+ @Mock
+ private FingerprintStateCallback mFingerprintStateCallback;
private LockoutResetDispatcher mLockoutResetDispatcher;
private Fingerprint21 mFingerprint21;
@@ -96,8 +99,9 @@
componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
resetLockoutRequiresHardwareAuthToken);
- mFingerprint21 = new TestableFingerprint21(mContext, sensorProps, mScheduler,
- new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController);
+ mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
+ mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
+ mHalResultController);
}
@Test
@@ -118,11 +122,13 @@
private static class TestableFingerprint21 extends Fingerprint21 {
TestableFingerprint21(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
- super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+ super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 7b20bf0..3ac30d02 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2960,9 +2960,6 @@
assertThat(intent.getAction()).isEqualTo(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, -1))
.isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID));
- assertThat(
- (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN))
- .isEqualTo(admin1);
assertThat(intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION))
.isEqualTo(UserManager.DISALLOW_ADJUST_VOLUME);
@@ -2999,7 +2996,7 @@
assertThat(intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION))
.isEqualTo(DevicePolicyManager.POLICY_DISABLE_CAMERA);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, -1))
- .isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID));
+ .isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_UID));
// ScreenCapture should not be disabled by device owner
intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
assertThat(intent).isNull();
@@ -7753,6 +7750,12 @@
DpmMockContext.CALLER_SYSTEM_USER_UID, admin1.getPackageName(), MODE_DEFAULT);
}
+ @Test
+ public void testGetOrganizationNameForUser_calledByNonPrivilegedApp_throwsException() {
+ assertExpectException(SecurityException.class, "Calling identity is not authorized",
+ () -> dpm.getOrganizationNameForUser(UserHandle.USER_SYSTEM));
+ }
+
private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
userVpnUid, List.of(new AppOpsManager.OpEntry(
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 0dd5c61..418831f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -26,6 +26,7 @@
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
import static com.google.common.truth.Truth.assertThat;
@@ -74,6 +75,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
@@ -110,6 +112,7 @@
private static final boolean DEBUG = false;
private static final float FLOAT_TOLERANCE = 0.01f;
private static final int DISPLAY_ID = 0;
+ private static final float TRANSITION_POINT = 0.763f;
private Context mContext;
private FakesInjector mInjector;
@@ -751,19 +754,27 @@
director.start(sensorManager);
- ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor<DisplayListener> displayListenerCaptor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ DisplayListener displayListener = displayListenerCaptor.getValue();
+
+ ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
.registerListener(
- listenerCaptor.capture(),
+ sensorListenerCaptor.capture(),
eq(lightSensor),
anyInt(),
any(Handler.class));
- SensorEventListener listener = listenerCaptor.getValue();
+ SensorEventListener sensorListener = sensorListenerCaptor.getValue();
- setBrightness(10);
+ setBrightness(10, 10, displayListener);
// Sensor reads 20 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertVoteForRefreshRate(vote, 90 /*fps*/);
@@ -771,9 +782,11 @@
assertThat(vote).isNotNull();
assertThat(vote.disableRefreshRateSwitching).isTrue();
- setBrightness(125);
+ // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+ // parameter to the necessary threshold
+ setBrightness(10, 125, displayListener);
// Sensor reads 1000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertThat(vote).isNull();
@@ -799,6 +812,14 @@
director.start(sensorManager);
+ ArgumentCaptor<DisplayListener> displayListenerCaptor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ DisplayListener displayListener = displayListenerCaptor.getValue();
+
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
@@ -807,20 +828,22 @@
eq(lightSensor),
anyInt(),
any(Handler.class));
- SensorEventListener listener = listenerCaptor.getValue();
+ SensorEventListener sensorListener = listenerCaptor.getValue();
- setBrightness(100);
+ setBrightness(100, 100, displayListener);
// Sensor reads 2000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertThat(vote).isNull();
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
assertThat(vote).isNull();
- setBrightness(255);
+ // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+ // parameter to the necessary threshold
+ setBrightness(100, 255, displayListener);
// Sensor reads 9000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertVoteForRefreshRate(vote, 60 /*fps*/);
@@ -1435,16 +1458,58 @@
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
- // Turn on HBM
+ // Turn on HBM, with brightness in the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR));
+ new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, hbmRefreshRate);
+
+ // Turn on HBM, with brightness below the HBM range
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HBM, with brightness in the HBM range
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, hbmRefreshRate);
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HBM, with brightness below the HBM range
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1514,7 +1579,8 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, initialRefreshRate);
@@ -1531,14 +1597,16 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
// Turn HBM on again and ensure the updated vote value stuck
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, updatedRefreshRate);
@@ -1553,7 +1621,8 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1584,14 +1653,82 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+ }
+
+ @Test
+ public void testHbmVoting_HbmUnsupported() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+ director.start(createMockSensorManager());
+
+ ArgumentCaptor<DisplayListener> captor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ DisplayListener listener = captor.getValue();
+
+ // Specify Limitation
+ when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
+ List.of(new RefreshRateLimitation(
+ DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+ 60.0f, 60.0f)));
+
+ // Verify that there is no HBM vote initially
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HBM when HBM is supported; expect a valid transition point and a vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, 60.0f);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on Sunlight HBM when HBM is unsupported; expect an invalid transition point and
+ // no vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ HBM_TRANSITION_POINT_INVALID));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HDR HBM when HBM is unsupported; expect an invalid transition point and
+ // no vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
+ HBM_TRANSITION_POINT_INVALID));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1600,7 +1737,7 @@
private void setHbmAndAssertRefreshRate(
DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
when(mInjector.getBrightnessInfo(DISPLAY_ID))
- .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode));
+ .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
@@ -1679,7 +1816,8 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, 60.f);
@@ -1834,11 +1972,14 @@
}
}
- private void setBrightness(int brightness) {
- Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS,
- brightness);
- mInjector.notifyBrightnessChanged();
- waitForIdleSync();
+ private void setBrightness(int brightness, int adjustedBrightness, DisplayListener listener) {
+ float floatBri = BrightnessSynchronizer.brightnessIntToFloat(brightness);
+ float floatAdjBri = BrightnessSynchronizer.brightnessIntToFloat(adjustedBrightness);
+
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
}
private void setPeakRefreshRate(float fps) {
@@ -1902,27 +2043,6 @@
}
@Override
- public void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- if (mBrightnessObserver != null) {
- throw new IllegalStateException("Tried to register a second brightness observer");
- }
- mBrightnessObserver = observer;
- }
-
- @Override
- public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- mBrightnessObserver = null;
- }
-
- void notifyBrightnessChanged() {
- if (mBrightnessObserver != null) {
- mBrightnessObserver.dispatchChange(false /*selfChange*/, DISPLAY_BRIGHTNESS_URI);
- }
- }
-
- @Override
public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
mPeakRefreshRateObserver = observer;
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index cc3591c8..aca8632 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -20,6 +20,8 @@
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.spy;
@@ -124,6 +126,7 @@
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
DEFAULT_MAX, null, () -> {}, mContextSpy);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+ assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@Test
@@ -135,6 +138,7 @@
hbmc.setAutoBrightnessEnabled(true);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+ assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
index acd3fca..3261dfa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
@@ -125,7 +125,7 @@
public void checkDeviceIdentifierAccess_hasPrivilegedPermission_returnsGranted() {
// Apps with the READ_PRIVILEGED_PHONE_STATE permission should have access to device
// identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
APP_PID, APP_UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -140,7 +140,7 @@
public void checkDeviceIdentifierAccess_hasAppOp_returnsGranted() {
// Apps that have been granted the READ_DEVICE_IDENTIFIERS appop should have access to
// device identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS),
eq(APP_UID), eq(mPackageName), any(), any())).thenReturn(
AppOpsManager.MODE_ALLOWED);
@@ -156,7 +156,7 @@
public void checkDeviceIdentifierAccess_hasDpmAccess_returnsGranted() {
// Apps that pass a DevicePolicyManager device / profile owner check should have access to
// device identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mDevicePolicyManager.hasDeviceIdentifierAccess(mPackageName, APP_PID,
APP_UID)).thenReturn(true);
@@ -236,7 +236,7 @@
// both the permission and the appop must be granted. If the permission is granted but the
// appop is not then AppOpsManager#MODE_IGNORED should be returned to indicate that this
// should be a silent failure.
- setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
setPackageTargetSdk(Build.VERSION_CODES.Q);
grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null);
@@ -256,7 +256,7 @@
// Apps targeting R+ with just the READ_PHONE_STATE permission granted should not have
// access to the phone number; PERMISSION_DENIED should be returned both with and without
// the appop granted since this check should be skipped for target SDK R+.
- setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null);
int resultWithoutAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess(
@@ -319,12 +319,79 @@
assertEquals(PackageManager.PERMISSION_GRANTED, resultWithAppop);
}
+ @Test
+ public void checkPhoneNumberAccess_providedUidDoesNotMatchPackageUid_throwsException()
+ throws Exception {
+ // An app can directly interact with one of the services that accepts a package name and
+ // returns a protected resource via a direct binder transact. This app could then provide
+ // the name of another app that targets pre-R, then determine if the app is installed based
+ // on whether the service throws an exception or not. While the app can provide the package
+ // name of another app, it cannot specify the package uid which is passed to the
+ // LegacyPermissionManager using Binder#getCallingUid. Ultimately this uid should then be
+ // compared against the actual uid of the package to ensure information about packages
+ // installed on the device is not leaked.
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID + 1);
+
+ assertThrows(SecurityException.class,
+ () -> mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName,
+ CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID));
+ }
+
+ @Test
+ public void checkPhoneNumberAccess_nullPackageNameSystemUid_returnsGranted() throws Exception {
+ // The platform can pass a null package name when checking if the platform itself has
+ // access to the device phone number(s) / identifier(s). This test ensures if a null package
+ // is provided, then the package uid check is skipped and the test is based on whether the
+ // the provided uid / pid has been granted the privileged permission.
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, -1);
+ when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ SYSTEM_PID, SYSTEM_UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(null,
+ CHECK_PHONE_NUMBER_MESSAGE, null, SYSTEM_PID, SYSTEM_UID);
+
+ assertEquals(PackageManager.PERMISSION_GRANTED, result);
+ }
+
+ @Test
+ public void checkPhoneNumberAccess_systemUidMismatchPackageUid_returnsGranted()
+ throws Exception {
+ // When the platform is checking device phone number / identifier access checks for other
+ // components on the platform, a uid less than the first application UID is provided; this
+ // test verifies the package uid check is skipped and access is still granted with the
+ // privileged permission.
+ int telephonyUid = SYSTEM_UID + 1;
+ int telephonyPid = SYSTEM_PID + 1;
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, -1);
+ when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ telephonyPid, telephonyUid)).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName,
+ CHECK_PHONE_NUMBER_MESSAGE, null, telephonyPid, telephonyUid);
+
+ assertEquals(PackageManager.PERMISSION_GRANTED, result);
+ }
+
/**
* Configures device identifier access tests to fail; tests verifying access should individually
* set an access check to succeed to verify access when that condition is met.
*/
private void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid) {
- setupAccessTest(callingPid, callingUid);
+ setupCheckDeviceIdentifierAccessTest(callingPid, callingUid, callingUid);
+ }
+
+ /**
+ * Configures device identifier access tests to fail; tests verifying access should individually
+ * set an access check to succeed to verify access when that condition is met.
+ *
+ * <p>To prevent leaking package information, access checks for package UIDs >= {@link
+ * android.os.Process#FIRST_APPLICATION_UID} must ensure the provided uid matches the uid of
+ * the package being checked; to ensure this check is successful, this method accepts the
+ * {@code packageUid} to be used for the package being checked.
+ */
+ public void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid,
+ int packageUid) {
+ setupAccessTest(callingPid, callingUid, packageUid);
when(mDevicePolicyManager.hasDeviceIdentifierAccess(anyString(), anyInt(),
anyInt())).thenReturn(false);
@@ -333,11 +400,26 @@
}
/**
- * Configures phone number access tests to fail; tests verifying access should individually set
- * an access check to succeed to verify access when that condition is met.
+ * Configures phone number access tests to fail; tests verifying access should individually
+ * set an access check to succeed to verify access when that condition is set.
+ *
*/
private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid) throws Exception {
- setupAccessTest(callingPid, callingUid);
+ setupCheckPhoneNumberAccessTest(callingPid, callingUid, callingUid);
+ }
+
+ /**
+ * Configures phone number access tests to fail; tests verifying access should individually set
+ * an access check to succeed to verify access when that condition is met.
+ *
+ * <p>To prevent leaking package information, access checks for package UIDs >= {@link
+ * android.os.Process#FIRST_APPLICATION_UID} must ensure the provided uid matches the uid of
+ * the package being checked; to ensure this check is successful, this method accepts the
+ * {@code packageUid} to be used for the package being checked.
+ */
+ private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid, int packageUid)
+ throws Exception {
+ setupAccessTest(callingPid, callingUid, packageUid);
setPackageTargetSdk(Build.VERSION_CODES.R);
}
@@ -345,9 +427,10 @@
* Configures the common mocks for any access tests using the provided {@code callingPid}
* and {@code callingUid}.
*/
- private void setupAccessTest(int callingPid, int callingUid) {
+ private void setupAccessTest(int callingPid, int callingUid, int packageUid) {
when(mInjector.getCallingPid()).thenReturn(callingPid);
when(mInjector.getCallingUid()).thenReturn(callingUid);
+ when(mInjector.getPackageUidForUser(anyString(), anyInt())).thenReturn(packageUid);
when(mInjector.checkPermission(anyString(), anyInt(), anyInt())).thenReturn(
PackageManager.PERMISSION_DENIED);
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
index 5012ca9..6e3f754 100644
--- a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
@@ -210,7 +210,7 @@
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return mNotifierMock;
}
@@ -298,6 +298,7 @@
BatteryStats.SERVICE_NAME)),
mInjector.createSuspendBlocker(mService, "testBlocker"),
null,
+ null,
null);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 5eabc1b..3d64d4b 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -215,7 +215,7 @@
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return mNotifierMock;
}
diff --git a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
index 11554c7..6334e9d 100644
--- a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
@@ -82,6 +82,8 @@
MockitoAnnotations.initMocks(this);
when(mUwbInjector.getVendorService()).thenReturn(mVendorService);
when(mUwbInjector.checkUwbRangingPermissionForDataDelivery(any(), any())).thenReturn(true);
+ when(mUwbInjector.isPersistedUwbStateEnabled()).thenReturn(true);
+ when(mUwbInjector.isAirplaneModeOn()).thenReturn(false);
when(mVendorService.asBinder()).thenReturn(mVendorServiceBinder);
mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 71c05b5..ea46eab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -32,6 +32,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
@@ -72,6 +73,7 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -1182,6 +1184,7 @@
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
mService.buzzBeepBlinkLocked(r);
+ verifyDelayedVibrate(mService.getVibratorHelper().createFallbackVibration(false));
// quiet update should stop making noise
mService.buzzBeepBlinkLocked(s);
@@ -1564,6 +1567,32 @@
}
@Test
+ public void testRingtoneInsistentBeep_canUpdate() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ mService.addNotification(ringtoneNotification);
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+ verifyDelayedVibrateLooped();
+ Mockito.reset(mVibrator);
+ Mockito.reset(mRingtonePlayer);
+
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+
+ // beep wasn't reset
+ verifyNeverBeep();
+ verifyNeverVibrate();
+ verify(mRingtonePlayer, never()).stopAsync();
+ verify(mVibrator, never()).cancel();
+ }
+
+ @Test
public void testCannotInterruptRingtoneInsistentBuzz() {
NotificationChannel ringtoneChannel =
new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index f9663f2..987236c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -65,6 +65,7 @@
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -1320,16 +1321,15 @@
APPROVAL_BY_COMPONENT);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- service.registerSystemService(cn, 0);
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onNullBinding(cn);
- return true;
- });
+ ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+ when(context.bindServiceAsUser(any(), captor.capture(), anyInt(), any()))
+ .thenAnswer(invocation -> {
+ captor.getValue().onNullBinding(cn);
+ return true;
+ });
service.registerSystemService(cn, 0);
- assertFalse(service.isBound(cn, 0));
+ verify(context).unbindService(captor.getValue());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f57c416..1ae219d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -516,7 +516,7 @@
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
- mWorkerHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
+ mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
@@ -2703,6 +2703,42 @@
}
@Test
+ public void testCrossUserSnooze() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 10);
+ mService.addNotification(r);
+ NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, 0);
+ mService.addNotification(r2);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+
+ verify(mWorkerHandler, never()).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ }
+
+ @Test
+ public void testSameUserSnooze() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 10);
+ mService.addNotification(r);
+ NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, 0);
+ mService.addNotification(r2);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(r2.getKey(), 1000, null, mListener);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ }
+
+ @Test
public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() throws Exception {
final NotificationRecord notification = generateNotificationRecord(
mTestNotificationChannel, 1, null, true);
@@ -3975,6 +4011,80 @@
}
@Test
+ public void testApplyAdjustmentsLogged() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r1 = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r1);
+ final NotificationRecord r2 = generateNotificationRecord(
+ mTestNotificationChannel, 2, null, true);
+ r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r2);
+
+ // Third notification that's NOT adjusted, just to make sure that doesn't get spuriously
+ // logged.
+ final NotificationRecord r3 = generateNotificationRecord(
+ mTestNotificationChannel, 3, null, true);
+ r3.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r3);
+
+ List<Adjustment> adjustments = new ArrayList<>();
+
+ // Test an adjustment that's associated with a ranking change and one that's not
+ Bundle signals1 = new Bundle();
+ signals1.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_HIGH);
+ Adjustment adjustment1 = new Adjustment(
+ r1.getSbn().getPackageName(), r1.getKey(), signals1, "",
+ r1.getUser().getIdentifier());
+ adjustments.add(adjustment1);
+
+ // This one wouldn't trigger a ranking change, but should still trigger a log.
+ Bundle signals2 = new Bundle();
+ signals2.putFloat(Adjustment.KEY_RANKING_SCORE, -0.5f);
+ Adjustment adjustment2 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals2, "",
+ r2.getUser().getIdentifier());
+ adjustments.add(adjustment2);
+
+ mBinderService.applyAdjustmentsFromAssistant(null, adjustments);
+ verify(mRankingHandler, times(1)).requestSort();
+
+ // Actually apply the adjustments & recalculate importance when run
+ doAnswer(invocationOnMock -> {
+ ((NotificationRecord) invocationOnMock.getArguments()[0])
+ .applyAdjustments();
+ ((NotificationRecord) invocationOnMock.getArguments()[0])
+ .calculateImportance();
+ return null;
+ }).when(mRankingHelper).extractSignals(any(NotificationRecord.class));
+
+ // Now make sure that when the sort happens, we actually log the changes.
+ mService.handleRankingSort();
+
+ // Even though the ranking score change is not meant to trigger a ranking update,
+ // during this process the package visibility & canShowBadge values are changing
+ // in all notifications, so all 3 seem to trigger a ranking change. Here we check instead
+ // that scheduleSendRankingUpdate is sent and that the relevant fields have been changed
+ // accordingly to confirm the adjustments happened to the 2 relevant notifications.
+ verify(handler, times(3)).scheduleSendRankingUpdate();
+ assertEquals(IMPORTANCE_HIGH, r1.getImportance());
+ assertTrue(r2.rankingScoreMatches(-0.5f));
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ mNotificationRecordLogger.event(0));
+ assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ mNotificationRecordLogger.event(1));
+ assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
+ assertEquals(2, mNotificationRecordLogger.get(1).getInstanceId());
+ }
+
+ @Test
public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addEnqueuedNotification(r);
@@ -4793,6 +4903,52 @@
}
@Test
+ public void testSetNotificationsShownFromListener_protectsCrossUserInformation()
+ throws RemoteException {
+ Notification.Builder nb = new Notification.Builder(
+ mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag" + System.currentTimeMillis(), UserHandle.PER_USER_RANGE, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE),
+ null, 0);
+ final NotificationRecord r =
+ new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ // no security exception!
+ mBinderService.setNotificationsShownFromListener(null, new String[] {r.getKey()});
+
+ verify(mAppUsageStats, never()).reportInterruptiveNotification(
+ anyString(), anyString(), anyInt());
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_protectsCrossUserInformation()
+ throws RemoteException {
+ Notification.Builder nb = new Notification.Builder(
+ mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag" + System.currentTimeMillis(), UserHandle.PER_USER_RANGE, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE),
+ null, 0);
+ final NotificationRecord r =
+ new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ // no security exception!
+ mBinderService.cancelNotificationsFromListener(null, new String[] {r.getKey()});
+
+ waitForIdle();
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+ @Test
public void testMaybeRecordInterruptionLocked_doesNotRecordTwice()
throws RemoteException {
final NotificationRecord r = generateNotificationRecord(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index 64fd19e..8a11798 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -45,6 +45,15 @@
groupInstanceId = groupId;
}
+ CallRecord(NotificationRecord r, int position, int buzzBeepBlink, InstanceId groupId) {
+ super(r, null);
+ this.position = position;
+ this.buzzBeepBlink = buzzBeepBlink;
+ wasLogged = true;
+ event = NotificationReportedEvent.NOTIFICATION_ADJUSTED;
+ groupInstanceId = groupId;
+ }
+
CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) {
super(r, null);
wasLogged = true;
@@ -75,6 +84,12 @@
}
@Override
+ public void logNotificationAdjusted(NotificationRecord r, int position, int buzzBeepBlink,
+ InstanceId groupId) {
+ mCalls.add(new CallRecord(r, position, buzzBeepBlink, groupId));
+ }
+
+ @Override
public void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
mCalls.add(new CallRecord(r, event));
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index bf0ed71..66d1577 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -91,6 +91,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.service.notification.ConversationChannelWrapper;
@@ -376,27 +377,19 @@
when(mPm.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
}
- private static NotificationChannel createNotificationChannel(String id, String name,
- int importance) {
- NotificationChannel channel = new NotificationChannel(id, name, importance);
- channel.setSound(SOUND_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT);
- return channel;
- }
-
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
// Setup package notifications.
String package0 = "test.package.user0";
int uid0 = 1001;
setUpPackageWithUid(package0, uid0);
- NotificationChannel channel0 = createNotificationChannel("id0", "name0", IMPORTANCE_HIGH);
+ NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
String package10 = "test.package.user10";
int uid10 = 1001001;
setUpPackageWithUid(package10, uid10);
- NotificationChannel channel10 = createNotificationChannel("id10", "name10",
- IMPORTANCE_HIGH);
+ NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));
ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10);
@@ -421,7 +414,7 @@
String package0 = "test.package.user0";
int uid0 = 1001;
setUpPackageWithUid(package0, uid0);
- NotificationChannel channel0 = createNotificationChannel("id0", "name0", IMPORTANCE_HIGH);
+ NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0);
@@ -514,8 +507,9 @@
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 = createNotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setDescription("descriptions for all");
channel2.setSound(SOUND_URI, mAudioAttributes);
channel2.enableLights(true);
@@ -524,7 +518,7 @@
channel2.enableVibration(false);
channel2.setGroup(ncg.getId());
channel2.setLightColor(Color.BLUE);
- NotificationChannel channel3 = createNotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
+ NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
channel3.enableVibration(true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -631,8 +625,7 @@
}
@Test
- public void testRestoreXml_withNonExistentCanonicalizedSoundUri_ignoreChannel()
- throws Exception {
+ public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
Thread.sleep(3000);
doReturn(null)
.when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
@@ -650,7 +643,7 @@
NotificationChannel actualChannel = mHelper.getNotificationChannel(
PKG_N_MR1, UID_N_MR1, channel.getId(), false);
- assertNull(actualChannel);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
}
@@ -659,8 +652,7 @@
* handle its restore properly.
*/
@Test
- public void testRestoreXml_withUncanonicalizedNonLocalSoundUri_ignoreChannel()
- throws Exception {
+ public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
// Not a local uncanonicalized uri, simulating that it fails to exist locally
doReturn(null)
.when(mTestIContentProvider).canonicalize(any(), eq(SOUND_URI));
@@ -679,7 +671,7 @@
backupWithUncanonicalizedSoundUri.getBytes(), true, UserHandle.USER_SYSTEM);
NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, id, false);
- assertNull(actualChannel);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
}
@Test
@@ -703,11 +695,11 @@
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
- createNotificationChannel("id2", "name2", IMPORTANCE_HIGH);
+ new NotificationChannel("id2", "name2", IMPORTANCE_HIGH);
NotificationChannel channel3 =
- createNotificationChannel("id3", "name3", IMPORTANCE_LOW);
+ new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
channel3.setGroup(ncg.getId());
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -3062,7 +3054,7 @@
@Test
public void testChannelXml_backupDefaultApp() throws Exception {
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
mHelper.createNotificationChannel(PKG_O, UID_O, channel1, true, false);
@@ -3343,7 +3335,7 @@
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
- PKG_P, UID_P, createNotificationChannel("id", "id", 2), true, false);
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
assertTrue(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id"));
assertFalse(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id"));
}
@@ -3354,7 +3346,7 @@
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
- PKG_P, UID_P, createNotificationChannel("id", "id", 2), true, false);
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
mHelper.deleteNotificationChannel(PKG_P, UID_P, "id");
NotificationChannel nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
assertTrue(DateUtils.isToday(nc1.getDeletedTimeMs()));
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index 222c692..6b36fe8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -69,7 +69,7 @@
@Before
public void setUp() {
- mDetector = new SingleKeyGestureDetector(mContext);
+ mDetector = new SingleKeyGestureDetector();
initSingleKeyGestureRules();
mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50;
mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50;
@@ -78,7 +78,7 @@
}
private void initSingleKeyGestureRules() {
- mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER,
+ mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(mContext, KEYCODE_POWER,
KEY_LONGPRESS | KEY_VERYLONGPRESS) {
@Override
int getMaxMultiPressCount() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 0d177c1..19f9b75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -40,8 +41,10 @@
import android.app.WaitResult;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.ConditionVariable;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
@@ -187,6 +190,24 @@
verify(taskChangeNotifier, never()).notifyActivityDismissingDockedRootTask();
}
+ /** Ensures that the calling package name passed to client complies with package visibility. */
+ @Test
+ public void testFilteredReferred() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setLaunchedFromPackage("other.package").setCreateTask(true).build();
+ assertNotNull(activity.launchedFromPackage);
+ try {
+ mSupervisor.realStartActivityLocked(activity, activity.app, false /* andResume */,
+ false /* checkConfig */);
+ } catch (RemoteException ignored) {
+ }
+ verify(activity).getFilteredReferrer(eq(activity.launchedFromPackage));
+
+ activity.deliverNewIntentLocked(ActivityBuilder.DEFAULT_FAKE_UID,
+ new Intent(), null /* intentGrants */, "other.package2");
+ verify(activity).getFilteredReferrer(eq("other.package2"));
+ }
+
/**
* Ensures that notify focus task changes.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 5a6581f..51e289f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1565,6 +1565,12 @@
mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
assertTrue(displayRotation.updateRotationUnchecked(false));
+ // Rotation can be updated if the policy is not ok to animate (e.g. going to sleep).
+ mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
+ displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
+ ((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false;
+ assertTrue(displayRotation.updateRotationUnchecked(false));
+
// Rotation can be updated if the recents animation is animating but it is not on top, e.g.
// switching activities in different orientations by quickstep gesture.
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index f2418c6..a8ede13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -82,7 +82,7 @@
mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken,
dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
null /* options */);
- return true;
+ return dc.getImeContainer().getConfiguration();
}).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG),
anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5880899..611b3f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -801,6 +801,7 @@
private int mConfigChanges;
private int mLaunchedFromPid;
private int mLaunchedFromUid;
+ private String mLaunchedFromPackage;
private WindowProcessController mWpc;
private Bundle mIntentExtras;
private boolean mOnTop = false;
@@ -911,6 +912,11 @@
return this;
}
+ ActivityBuilder setLaunchedFromPackage(String packageName) {
+ mLaunchedFromPackage = packageName;
+ return this;
+ }
+
ActivityBuilder setUseProcess(WindowProcessController wpc) {
mWpc = wpc;
return this;
@@ -1000,6 +1006,7 @@
final ActivityRecord activity = new ActivityRecord.Builder(mService)
.setLaunchedFromPid(mLaunchedFromPid)
.setLaunchedFromUid(mLaunchedFromUid)
+ .setLaunchedFromPackage(mLaunchedFromPackage)
.setIntent(intent)
.setActivityInfo(aInfo)
.setActivityOptions(options)
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 7b6ccd3..0c65cc4 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -45,6 +45,7 @@
import android.service.usb.UsbUserPermissionsManagerProto;
import android.util.ArrayMap;
import android.util.AtomicFile;
+import android.util.EventLog;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.TypedXmlPullParser;
@@ -74,6 +75,8 @@
private static final String TAG = UsbUserPermissionManager.class.getSimpleName();
private static final boolean DEBUG = false;
+ private static final int SNET_EVENT_LOG_ID = 0x534e4554;
+
@GuardedBy("mLock")
/** Mapping of USB device name to list of UIDs with permissions for the device
* Each entry lasts until device is disconnected*/
@@ -689,8 +692,11 @@
try {
ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
if (aInfo.uid != uid) {
- throw new IllegalArgumentException("package " + packageName
+ Slog.w(TAG, "package " + packageName
+ " does not match caller's uid " + uid);
+ EventLog.writeEvent(SNET_EVENT_LOG_ID, "180104273", -1, "");
+ throw new IllegalArgumentException("package " + packageName
+ + " not found");
}
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("package " + packageName + " not found");
diff --git a/services/uwb/java/com/android/server/uwb/UwbInjector.java b/services/uwb/java/com/android/server/uwb/UwbInjector.java
index 64f1da1..a7a0500 100644
--- a/services/uwb/java/com/android/server/uwb/UwbInjector.java
+++ b/services/uwb/java/com/android/server/uwb/UwbInjector.java
@@ -21,10 +21,13 @@
import android.annotation.NonNull;
import android.content.AttributionSource;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.PermissionChecker;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.provider.Settings;
+import android.uwb.AdapterState;
import android.uwb.IUwbAdapter;
@@ -80,4 +83,23 @@
mContext, UWB_RANGING, -1, attributionSource, message);
return permissionCheckResult == PERMISSION_GRANTED;
}
+
+ /** Returns true if UWB state saved in Settings is enabled. */
+ public boolean isPersistedUwbStateEnabled() {
+ final ContentResolver cr = mContext.getContentResolver();
+ try {
+ return Settings.Global.getInt(cr, Settings.Global.UWB_ENABLED)
+ == AdapterState.STATE_ENABLED_ACTIVE;
+ } catch (Settings.SettingNotFoundException e) {
+ Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED,
+ AdapterState.STATE_ENABLED_ACTIVE);
+ return true;
+ }
+ }
+
+ /** Returns true if airplane mode is turned on. */
+ public boolean isAirplaneModeOn() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
}
diff --git a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
index 4dd26a6..70d6aab 100644
--- a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
+++ b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
@@ -18,13 +18,19 @@
import android.annotation.NonNull;
import android.content.AttributionSource;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Binder;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
+import android.uwb.AdapterState;
import android.uwb.IUwbAdapter;
import android.uwb.IUwbAdapterStateCallbacks;
import android.uwb.IUwbRangingCallbacks;
@@ -225,13 +231,20 @@
mVendorUwbAdapter = null;
}
- private synchronized IUwbAdapter getVendorUwbAdapter() throws IllegalStateException {
+ private synchronized IUwbAdapter getVendorUwbAdapter()
+ throws IllegalStateException, RemoteException {
if (mVendorUwbAdapter != null) return mVendorUwbAdapter;
mVendorUwbAdapter = mUwbInjector.getVendorService();
if (mVendorUwbAdapter == null) {
throw new IllegalStateException("No vendor service found!");
}
Log.i(TAG, "Retrieved vendor service");
+ long token = Binder.clearCallingIdentity();
+ try {
+ mVendorUwbAdapter.setEnabled(isEnabled());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
linkToVendorServiceDeath();
return mVendorUwbAdapter;
}
@@ -239,6 +252,7 @@
UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) {
mContext = context;
mUwbInjector = uwbInjector;
+ registerAirplaneModeReceiver();
}
private void enforceUwbPrivilegedPermission() {
@@ -320,6 +334,34 @@
@Override
public synchronized void setEnabled(boolean enabled) throws RemoteException {
- getVendorUwbAdapter().setEnabled(enabled);
+ persistUwbState(enabled);
+ getVendorUwbAdapter().setEnabled(isEnabled());
+ }
+
+ private void persistUwbState(boolean enabled) {
+ final ContentResolver cr = mContext.getContentResolver();
+ int state = enabled ? AdapterState.STATE_ENABLED_ACTIVE : AdapterState.STATE_DISABLED;
+ Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED, state);
+ }
+
+ private void registerAirplaneModeReceiver() {
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleAirplaneModeEvent();
+ }
+ }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ }
+
+ private void handleAirplaneModeEvent() {
+ try {
+ getVendorUwbAdapter().setEnabled(isEnabled());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to set UWB Adapter state.", e);
+ }
+ }
+
+ private boolean isEnabled() {
+ return mUwbInjector.isPersistedUwbStateEnabled() && !mUwbInjector.isAirplaneModeOn();
}
}
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 702ddb1..af0eca9 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -18,5 +18,9 @@
name: "services.voiceinteraction",
defaults: ["platform_service_defaults"],
srcs: [":services.voiceinteraction-sources"],
- libs: ["services.core"],
+ libs: [
+ "services.core",
+ "android.hardware.power-V1-java",
+ "android.hardware.power-V1.0-java",
+ ],
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index a9aeb98..4dc83ae 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -269,13 +269,11 @@
Slog.v(TAG, "cancelLocked");
clearDebugHotwordLoggingTimeoutLocked();
mDebugHotwordLogging = false;
- if (mRemoteHotwordDetectionService.isBound()) {
- mRemoteHotwordDetectionService.unbind();
- LocalServices.getService(PermissionManagerServiceInternal.class)
- .setHotwordDetectionServiceProvider(null);
- mIdentity = null;
- updateServiceUidForAudioPolicy(Process.INVALID_UID);
- }
+ mRemoteHotwordDetectionService.unbind();
+ LocalServices.getService(PermissionManagerServiceInternal.class)
+ .setHotwordDetectionServiceProvider(null);
+ mIdentity = null;
+ updateServiceUidForAudioPolicy(Process.INVALID_UID);
mCancellationTaskFuture.cancel(/* may interrupt */ true);
if (mAudioFlinger != null) {
mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index cc021a9..08e9703 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -43,11 +43,13 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
+import android.hardware.power.Boost;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -62,6 +64,7 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.am.AssistDataRequester;
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
@@ -70,13 +73,20 @@
import com.android.server.wm.ActivityAssistInfo;
import java.io.PrintWriter;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
final class VoiceInteractionSessionConnection implements ServiceConnection,
AssistDataRequesterCallbacks {
- final static String TAG = "VoiceInteractionServiceManager";
+ static final String TAG = "VoiceInteractionServiceManager";
+ static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt(
+ System.getProperty("vendor.powerhal.interaction.max", "200"));
+ static final int BOOST_TIMEOUT_MS = 300;
+ // TODO: To avoid ap doesn't call hide, only 10 secs for now, need a better way to manage it
+ // in the future.
+ static final int MAX_POWER_BOOST_TIMEOUT = 10_000;
final IBinder mToken = new Binder();
final Object mLock;
@@ -104,6 +114,45 @@
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>();
AssistDataRequester mAssistDataRequester;
+ private final PowerManagerInternal mPowerManagerInternal;
+ private PowerBoostSetter mSetPowerBoostRunnable;
+ private final Handler mFgHandler;
+
+ class PowerBoostSetter implements Runnable {
+
+ private boolean mCanceled;
+ private final Instant mExpiryTime;
+
+ PowerBoostSetter(Instant expiryTime) {
+ mExpiryTime = expiryTime;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return;
+ }
+ // To avoid voice interaction service does not call hide to cancel setting
+ // power boost. We will cancel set boost when reaching the max timeout.
+ if (Instant.now().isBefore(mExpiryTime)) {
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, BOOST_TIMEOUT_MS);
+ if (mSetPowerBoostRunnable != null) {
+ mFgHandler.postDelayed(mSetPowerBoostRunnable, POWER_BOOST_TIMEOUT_MS);
+ }
+ } else {
+ Slog.w(TAG, "Reset power boost INTERACTION because reaching max timeout.");
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
+ }
+ }
+ }
+
+ void cancel() {
+ synchronized (mLock) {
+ mCanceled = true;
+ }
+ }
+ }
IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -152,7 +201,9 @@
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOps = context.getSystemService(AppOpsManager.class);
+ mFgHandler = FgThread.getHandler();
mAssistDataRequester = new AssistDataRequester(mContext, mIWindowManager,
(AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
@@ -255,6 +306,13 @@
mPendingHandleAssistWithoutData = topActivities;
}
}
+ // remove if already existing one.
+ if (mSetPowerBoostRunnable != null) {
+ mSetPowerBoostRunnable.cancel();
+ }
+ mSetPowerBoostRunnable = new PowerBoostSetter(
+ Instant.now().plusMillis(MAX_POWER_BOOST_TIMEOUT));
+ mFgHandler.post(mSetPowerBoostRunnable);
mCallback.onSessionShown(this);
return true;
}
@@ -420,6 +478,12 @@
} catch (RemoteException e) {
}
}
+ if (mSetPowerBoostRunnable != null) {
+ mSetPowerBoostRunnable.cancel();
+ mSetPowerBoostRunnable = null;
+ }
+ // A negative value indicates canceling previous boost.
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
mCallback.onSessionHidden(this);
}
if (mFullyBound) {
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 02bd001..bc0a146 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -23,6 +23,8 @@
import android.os.Bundle;
import android.util.ArrayMap;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -115,6 +117,7 @@
public static final int SDK_VERSION_R = 30;
// A Map allows us to track each Call by its Telecom-specified call ID
+ @GuardedBy("mLock")
private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
// A List allows us to keep the Calls in a stable iteration order so that casually developed
@@ -154,7 +157,7 @@
return;
}
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call == null) {
call = new Call(this, parcelableCall.getId(), mInCallAdapter,
parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
@@ -191,14 +194,14 @@
if (mTargetSdkVersion < SDK_VERSION_R
&& parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
Log.i(this, "removing audio processing call during update for sdk compatibility");
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call != null) {
internalRemoveCall(call);
}
return;
}
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call != null) {
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
@@ -215,8 +218,14 @@
}
}
+ Call getCallById(String callId) {
+ synchronized (mLock) {
+ return mCallByTelecomCallId.get(callId);
+ }
+ }
+
final void internalSetPostDialWait(String telecomId, String remaining) {
- Call call = mCallByTelecomCallId.get(telecomId);
+ Call call = getCallById(telecomId);
if (call != null) {
call.internalSetPostDialWait(remaining);
}
@@ -230,7 +239,7 @@
}
final Call internalGetCallByTelecomId(String telecomId) {
- return mCallByTelecomCallId.get(telecomId);
+ return getCallById(telecomId);
}
final void internalBringToForeground(boolean showDialpad) {
@@ -249,35 +258,35 @@
}
final void internalOnConnectionEvent(String telecomId, String event, Bundle extras) {
- Call call = mCallByTelecomCallId.get(telecomId);
+ Call call = getCallById(telecomId);
if (call != null) {
call.internalOnConnectionEvent(event, extras);
}
}
final void internalOnRttUpgradeRequest(String callId, int requestId) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnRttUpgradeRequest(requestId);
}
}
final void internalOnRttInitiationFailure(String callId, int reason) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnRttInitiationFailure(reason);
}
}
final void internalOnHandoverFailed(String callId, int error) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnHandoverFailed(error);
}
}
final void internalOnHandoverComplete(String callId) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnHandoverComplete();
}
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 4df8a4b..f248fd5 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -8,10 +8,10 @@
tgunn@google.com
jminjie@google.com
shuoq@google.com
-nazaninb@google.com
sarahchin@google.com
xiaotonj@google.com
huiwang@google.com
jayachandranc@google.com
chinmayd@google.com
amruthr@google.com
+sasindran@google.com
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8e910c9..277ce15 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5161,6 +5161,16 @@
"display_no_data_notification_on_permanent_failure_bool";
/**
+ * Boolean indicating if the VoNR setting is visible in the Call Settings menu.
+ * If true, the VoNR setting menu will be visible. If false, the menu will be gone.
+ *
+ * Disabled by default.
+ *
+ * @hide
+ */
+ public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
+
+ /**
* Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
*
* @hide
@@ -5774,6 +5784,7 @@
sDefaults.putString(KEY_CARRIER_PROVISIONING_APP_STRING, "");
sDefaults.putBoolean(KEY_DISPLAY_NO_DATA_NOTIFICATION_ON_PERMANENT_FAILURE_BOOL, false);
sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
+ sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, false);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 255a612..9896634 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3483,7 +3483,8 @@
*/
private int getLogicalSlotIndex(int physicalSlotIndex) {
UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
- if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length) {
+ if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length
+ && slotInfos[physicalSlotIndex] != null) {
return slotInfos[physicalSlotIndex].getLogicalSlotIdx();
}
@@ -8853,7 +8854,8 @@
}
/**
- * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
+ * Set the preferred network type to global mode which includes NR, LTE, CDMA, EvDo
+ * and GSM/WCDMA.
*
* <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
@@ -8864,7 +8866,8 @@
}
/**
- * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
+ * Set the preferred network type to global mode which includes NR, LTE, CDMA, EvDo
+ * and GSM/WCDMA.
*
* <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
@@ -8872,7 +8875,7 @@
* @hide
*/
public boolean setPreferredNetworkTypeToGlobal(int subId) {
- return setPreferredNetworkType(subId, RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+ return setPreferredNetworkType(subId, RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
}
/**
@@ -12143,6 +12146,100 @@
}
/**
+ * No error. Operation succeeded.
+ * @hide
+ */
+ public static final int ENABLE_VONR_SUCCESS = 0;
+
+ /**
+ * Radio is not available.
+ * @hide
+ */
+ public static final int ENABLE_VONR_RADIO_NOT_AVAILABLE = 2;
+
+ /**
+ * Internal Radio error.
+ * @hide
+ */
+ public static final int ENABLE_VONR_RADIO_ERROR = 3;
+
+ /**
+ * Voice over NR enable/disable request is received when system is in invalid state.
+ * @hide
+ */
+ public static final int ENABLE_VONR_RADIO_INVALID_STATE = 4;
+
+ /**
+ * Voice over NR enable/disable request is not supported.
+ * @hide
+ */
+ public static final int ENABLE_VONR_REQUEST_NOT_SUPPORTED = 5;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"EnableVoNrResult"}, value = {
+ ENABLE_VONR_SUCCESS,
+ ENABLE_VONR_RADIO_NOT_AVAILABLE,
+ ENABLE_VONR_RADIO_ERROR,
+ ENABLE_VONR_RADIO_INVALID_STATE,
+ ENABLE_VONR_REQUEST_NOT_SUPPORTED})
+ public @interface EnableVoNrResult {}
+
+ /**
+ * Enable or disable Voice over NR (VoNR)
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ *
+ * @param enabled enable or disable VoNR.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public @EnableVoNrResult int setVoNrEnabled(boolean enabled) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.setVoNrEnabled(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#setVoNrEnabled", e);
+ }
+
+ return ENABLE_VONR_RADIO_INVALID_STATE;
+ }
+
+ /**
+ * Is Voice over NR (VoNR) enabled.
+ * @return true if Voice over NR (VoNR) is enabled else false. Enabled state does not mean
+ * voice call over NR is active or voice ove NR is available. It means the device is allowed to
+ * register IMS over NR.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isVoNrEnabled() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.isVoNrEnabled(getSubId());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "isVoNrEnabled RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
* Carrier action to start or stop reporting default network available events.
*
* <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
@@ -14207,7 +14304,8 @@
if (callForwardingInfo.getNumber() == null) {
throw new IllegalArgumentException("callForwarding number is null");
}
- if (callForwardingInfo.getTimeoutSeconds() <= 0) {
+ if (callForwardingReason == CallForwardingInfo.REASON_NO_REPLY
+ && callForwardingInfo.getTimeoutSeconds() <= 0) {
throw new IllegalArgumentException("callForwarding timeout isn't positive");
}
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 63732b5..c47e776 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2229,6 +2229,20 @@
List<String> getEquivalentHomePlmns(int subId, String callingPackage, String callingFeatureId);
/**
+ * Enable or disable Voice over NR (VoNR)
+ * @param subId the subscription ID that this action applies to.
+ * @param enabled enable or disable VoNR.
+ * @return operation result.
+ */
+ int setVoNrEnabled(int subId, boolean enabled);
+
+ /**
+ * Is voice over NR enabled
+ * @return true if VoNR is enabled else false
+ */
+ boolean isVoNrEnabled(int subId);
+
+ /**
* Enable/Disable E-UTRA-NR Dual Connectivity
* @return operation result. See TelephonyManager.EnableNrDualConnectivityResult for
* details
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index b73f827..61c269c 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -528,6 +528,8 @@
int RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP = 222;
int RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP = 223;
int RIL_REQUEST_GET_SLICING_CONFIG = 224;
+ int RIL_REQUEST_ENABLE_VONR = 225;
+ int RIL_REQUEST_IS_VONR_ENABLED = 225;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;