diff options
227 files changed, 9682 insertions, 1444 deletions
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp index b2b66c27f795..3534624a58a2 100644 --- a/cmds/bootanimation/Android.bp +++ b/cmds/bootanimation/Android.bp @@ -71,7 +71,7 @@ cc_library_shared { "libui", "libjnigraphics", "libEGL", - "libGLESv1_CM", + "libGLESv2", "libgui", ], } diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 3109c5c1e075..7ed0bed43833 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 PROGRESS_PROP_NAME[] = "service.bootanim.progress"; 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.05f; + 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.25f), 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 @@ void BootAnimation::binderDied(const wp<IBinder>&) { 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 @@ static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitm 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 @@ static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitm } 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 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, 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 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, 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 @@ status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) { 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 @@ status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) { 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 @@ status_t BootAnimation::readyToRun() { 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 @@ status_t BootAnimation::readyToRun() { 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,71 @@ void BootAnimation::findBootAnimationFile() { } } +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); + 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 +777,8 @@ bool BootAnimation::threadLoop() { } 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 +787,14 @@ bool BootAnimation::android() { mCallbacks->init({}); // clear screen - glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); 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 +817,12 @@ bool BootAnimation::android() { 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 +917,20 @@ static bool parseColor(const char str[7], float color[3]) { 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 +963,10 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) { 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 +981,11 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) { 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 +1001,6 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* *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 +1012,13 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* 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 +1072,8 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) { return false; } char const* s = desString.string(); + std::string dynamicColoringPartName = ""; + bool postDynamicColoring = false; // Parse the description file for (;;) { @@ -952,11 +1088,19 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) { 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 +1115,18 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) { } 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 +1139,16 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) { // "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 +1332,16 @@ bool BootAnimation::movie() { // 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 +1356,10 @@ bool BootAnimation::movie() { mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL); } + if (mAnimation != nullptr && mAnimation->dynamicColoringEnabled) { + initDynamicColors(); + } + playAnimation(*mAnimation); if (mTimeCheckThread != nullptr) { @@ -1218,6 +1385,55 @@ bool BootAnimation::shouldStopPlayingPart(const Animation::Part& part, (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 +1446,6 @@ bool BootAnimation::playAnimation(const Animation& animation) { 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 +1476,19 @@ bool BootAnimation::playAnimation(const Animation& animation) { 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 +1500,38 @@ bool BootAnimation::playAnimation(const Animation& animation) { 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 f8a31c6d8790..7a597da533ee 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 @@ public: }; struct Font { - FileMap* map; + FileMap* map = nullptr; Texture texture; int char_width; int char_height; @@ -62,7 +62,7 @@ public: struct Animation { struct Frame { String8 name; - FileMap* map; + FileMap* map = nullptr; int trimX; int trimY; int trimWidth; @@ -90,6 +90,10 @@ public: 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 @@ public: 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 @@ private: 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 @@ private: 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 @@ private: void checkExit(); void handleViewport(nsecs_t timestep); + void initDynamicColors(); sp<SurfaceComposerClient> mSession; AssetManager mAssets; @@ -218,6 +233,13 @@ private: 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/GameManager.java b/core/java/android/app/GameManager.java index 5964f71d28db..babeb08caff7 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -135,6 +135,7 @@ public final class GameManager { throw e.rethrowFromSystemServer(); } } + /** * Returns a list of supported game modes for a given package. * <p> @@ -151,4 +152,20 @@ public final class GameManager { } } + /** + * 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 4bf8a3f77bca..189f0a2fca23 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -23,4 +23,5 @@ interface IGameManagerService { 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 4b054f49d910..e39636dc45fb 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5387,8 +5387,8 @@ public class Notification implements Parcelable 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); @@ -6247,8 +6247,9 @@ public class Notification implements Parcelable * * @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; } @@ -12305,6 +12306,8 @@ public class Notification implements Parcelable 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 +12365,7 @@ public class Notification implements Parcelable 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 +12382,8 @@ public class Notification implements Parcelable mContrastColor = mPrimaryTextColor; mPrimaryAccentColor = mPrimaryTextColor; mSecondaryAccentColor = mSecondaryTextColor; + mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor); + mOnAccentTextColor = mBackgroundColor; mErrorColor = mPrimaryTextColor; mRippleAlpha = 0x33; } else { @@ -12389,6 +12394,8 @@ public class Notification implements Parcelable R.attr.textColorSecondary, R.attr.colorAccent, R.attr.colorAccentSecondary, + R.attr.colorAccentTertiary, + R.attr.textColorOnAccent, R.attr.colorError, R.attr.colorControlHighlight }; @@ -12399,8 +12406,10 @@ public class Notification implements Parcelable 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 +12429,14 @@ public class Notification implements Parcelable 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 +12502,16 @@ public class Notification implements Parcelable 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 1837fb860b73..6553b61ebbc2 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -260,8 +260,6 @@ public final class NotificationChannel implements Parcelable { 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 @@ public final class NotificationChannel implements Parcelable { } /** - * @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 @@ public final class NotificationChannel implements Parcelable { // 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/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index dae565e12fd7..67f631f98f0b 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -281,6 +281,32 @@ public class BackupManager { } /** + * 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/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index a9b95fce8777..6c3936569c28 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -361,6 +361,11 @@ public final class DisplayManagerGlobal { 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 @@ public final class DisplayManagerGlobal { 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 @@ public final class DisplayManagerGlobal { } } - // 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/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index be21fea1d0df..1cceea30868c 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.IPackageManager; 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 dalvik.system.VMRuntime; 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 @@ public class GraphicsEnvironment { 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 @@ public class GraphicsEnvironment { private ClassLoader mClassLoader; private String mLibrarySearchPaths; private String mLibraryPermittedPaths; + private GameManager mGameManager; private int mAngleOptInIndex = -1; @@ -133,6 +127,8 @@ public class GraphicsEnvironment { 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 @@ public class GraphicsEnvironment { } /** + * 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 @@ public class GraphicsEnvironment { 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 @@ public class GraphicsEnvironment { } /** - * 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 @@ public class GraphicsEnvironment { 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; - } - - 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; + // 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); - return false; + return true; } /** @@ -956,7 +846,7 @@ public class GraphicsEnvironment { 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/provider/Settings.java b/core/java/android/provider/Settings.java index ac520e8b3dec..18e9e01bcf67 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13670,13 +13670,6 @@ public final class Settings { "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 +14908,16 @@ public final class Settings { "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/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java index 8808827b248a..758f9ab935cf 100644 --- a/core/java/android/view/ScrollCaptureResponse.java +++ b/core/java/android/view/ScrollCaptureResponse.java @@ -53,6 +53,10 @@ public class ScrollCaptureResponse implements Parcelable { @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 @@ public class ScrollCaptureResponse implements Parcelable { - // 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 @@ public class ScrollCaptureResponse implements Parcelable { @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 @@ public class ScrollCaptureResponse implements Parcelable { 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 @@ public class ScrollCaptureResponse implements Parcelable { } /** + * 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 @@ public class ScrollCaptureResponse implements Parcelable { "windowBounds = " + mWindowBounds + ", " + "boundsInWindow = " + mBoundsInWindow + ", " + "windowTitle = " + mWindowTitle + ", " + + "packageName = " + mPackageName + ", " + "messages = " + mMessages + " }"; } @@ -187,12 +202,14 @@ public class ScrollCaptureResponse implements Parcelable { 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 @@ public class ScrollCaptureResponse implements Parcelable { 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 @@ public class ScrollCaptureResponse implements Parcelable { 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 @@ public class ScrollCaptureResponse implements Parcelable { 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 @@ public class ScrollCaptureResponse implements Parcelable { } /** + * 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 @@ public class ScrollCaptureResponse implements Parcelable { /** 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 @@ public class ScrollCaptureResponse implements Parcelable { mWindowTitle = null; } if ((mBuilderFieldsSet & 0x20) == 0) { + mPackageName = null; + } + if ((mBuilderFieldsSet & 0x40) == 0) { mMessages = new ArrayList<>(); } ScrollCaptureResponse o = new ScrollCaptureResponse( @@ -366,12 +400,13 @@ public class ScrollCaptureResponse implements Parcelable { 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 @@ public class ScrollCaptureResponse implements Parcelable { } @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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3550a31f9038..985f20b5ca30 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -9493,6 +9493,7 @@ public final class ViewRootImpl implements ViewParent, 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/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java index 8b3c1337c0c8..7a712e50e6a8 100644 --- a/core/java/com/android/internal/util/ContrastColorUtil.java +++ b/core/java/com/android/internal/util/ContrastColorUtil.java @@ -291,10 +291,10 @@ public class ContrastColorUtil { * 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 @@ public class ContrastColorUtil { * @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 @@ public class ContrastColorUtil { * 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 @@ public class ContrastColorUtil { low = l; } } - return findFg ? fg : bg; + hsl[2] = high; + return ColorUtilsFromCompat.HSLToColor(hsl); } public static int ensureTextContrastOnBlack(int color) { @@ -452,7 +453,7 @@ public class ContrastColorUtil { } /** - * 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/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java index 0c2d2a9bcf41..3191e23a9883 100644 --- a/core/java/com/android/internal/widget/NotificationActionListLayout.java +++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java @@ -53,6 +53,8 @@ public class NotificationActionListLayout extends LinearLayout { 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 @@ public class NotificationActionListLayout extends LinearLayout { && ((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 @@ public class NotificationActionListLayout extends LinearLayout { otherViews++; } if (c.getVisibility() != GONE) { - notGoneChildren++; + mNumNotGoneChildren++; if (isPriority(c)) { - priorityChildren++; + mNumPriorityChildren++; } } } @@ -119,17 +120,20 @@ public class NotificationActionListLayout extends LinearLayout { 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 @@ public class NotificationActionListLayout extends LinearLayout { // 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 @@ public class NotificationActionListLayout extends LinearLayout { } 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 b40491a49b14..f44e829d49d7 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -50,8 +50,7 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, } 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 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appNa } } - 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 @@ const JNINativeMethod g_methods[] = { {"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 c3d159659622..6bc00e2281ef 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -771,6 +771,8 @@ message GlobalSettingsProto { 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; @@ -1063,5 +1065,5 @@ message GlobalSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 154; + // Next tag = 155; } diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml index 29c51f2a33c9..7c09fb889c48 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/config.xml b/core/res/res/values/config.xml index db43b5b31e7e..e1e1201d90df 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. --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index de7a1175b4a3..2baed4359ff6 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -237,6 +237,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/symbols.xml b/core/res/res/values/symbols.xml index adb046e76c88..6802ae551e82 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" /> @@ -3183,6 +3185,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" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index cd07d464ee65..685671b083c4 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -399,6 +399,8 @@ public class NotificationTest { 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,20 +415,26 @@ public class NotificationTest { 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); + + // The text that is used within the accent color DOES need to have contrast + assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5); } private void assertContrastIsAtLeast(int foreground, int background, double minContrast) { diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index dfc9013e3c05..149f58f0a69b 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.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; @@ -121,6 +122,46 @@ public class DisplayManagerGlobalTest { 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/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java new file mode 100644 index 000000000000..9da720cbfa87 --- /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); + } + + private void assertContrastIsWithinRange(int foreground, int background, + double minContrast, double maxContrast) { + assertContrastIsAtLeast(foreground, background, minContrast); + assertContrastIsAtMost(foreground, background, maxContrast); + } + + 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); + } + } + + private 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/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index c0df06f2954f..ac97c8f80617 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 @@ public class BubbleStackView extends FrameLayout 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 @@ public class BubbleStackView extends FrameLayout mRelativeStackPositionBeforeRotation = null; } - setUpDismissView(); if (mIsExpanded) { // Re-draw bubble row and pointer for new orientation. beforeExpandedViewAnimation(); @@ -1043,10 +1039,9 @@ public class BubbleStackView extends FrameLayout 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 0802fb59a008..636e1452aa9b 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 @@ public class StackAnimationController extends } /** - * 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 @@ public class StackAnimationController extends 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 9f6dd1f27b62..9e012598554b 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 @@ abstract class MagnetizedObject<T : Any>( } /** + * 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 d3274706631b..9e1c61aac868 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.content.Context; 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 android.window.DisplayAreaOrganizer; 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/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java index f58c6b173af9..81dd60d715e9 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.ColorStateList; 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.ImageView; 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/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java index a5e96d14dde6..20021ebea834 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.content.res.Configuration; 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 @@ import com.android.wm.shell.common.SyncTransactionQueue; 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 @@ class SizeCompatUILayout { 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 f634c4586e39..82f69c3e2985 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 @@ class SizeCompatUIWindowManager extends WindowlessWindowManager { } 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/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 48cdf1647d86..197b7b2b0eaf 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 5d7b9bb36f75..cef9014ec229 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 72fa25fc7a3a..bf0dc7bce5f9 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 @@ public class WifiStatusTracker { 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 @@ public class WifiStatusTracker { mNetworkScoreManager = networkScoreManager; mConnectivityManager = connectivityManager; mCallback = callback; - mSupportMergedUi = false; } public void setListening(boolean listening) { @@ -223,10 +221,8 @@ public class WifiStatusTracker { } 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 @@ public class WifiStatusTracker { } 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 61006156d8aa..56454e975370 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.NETWORK_ 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 @@ public class WifiUtils { 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 @@ public class WifiUtils { 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 89960cba2bf5..7c2b904fc576 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 com.google.common.truth.Truth.assertThat; 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.os.SystemClock; 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.junit.runner.RunWith; 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 @@ public class WifiUtilsTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; + mContext = spy(ApplicationProvider.getApplicationContext()); } @Test @@ -148,6 +152,32 @@ public class WifiUtilsTest { 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 eb8196176034..a46d28b273e5 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 @@ public class GlobalSettings { 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 60226084c70d..96f127b6a611 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 @@ public class SecureSettings { 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 @@ public class SecureSettings { 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 5220a04d73e6..84c5febcb5a2 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.media.AudioFormat.SURROUND_SOUND_ENCODING; 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 @@ public class GlobalSettingsValidators { /* 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 c57786888e8d..6cfcb51239a3 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 @@ public class SettingsHelper { * {@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 @@ public class SettingsHelper { 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 @@ public class SettingsHelper { // 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 @@ public class SettingsHelper { 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 @@ public class SettingsHelper { } 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 073b4d00653d..20735cba44a3 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 @@ class SettingsProtoDumpUtil { 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 @@ class SettingsProtoDumpUtil { 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, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 3297937e3e75..cd34924ddb46 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -509,7 +509,6 @@ public class SettingsBackupTest { 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, diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml index a4d0ff7269a2..1934457b4bc6 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/anim/progress_indeterminate_horizontal_rect.xml b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml new file mode 100644 index 000000000000..13133cb451e4 --- /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 000000000000..e8218259087d --- /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 000000000000..6d2133c5bacf --- /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 000000000000..21d1dccbf900 --- /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 000000000000..ba7848a5d23e --- /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 000000000000..438e4c70dc71 --- /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 000000000000..2c34060ccd61 --- /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_settings_24dp.xml b/packages/SystemUI/res/drawable/ic_settings_24dp.xml new file mode 100644 index 000000000000..ac4c43bd35b9 --- /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 000000000000..f38a36804bc8 --- /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 000000000000..3ceb0f6ac06a --- /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 000000000000..50267fda0b25 --- /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 000000000000..14672ef3dcfe --- /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/progress_indeterminate_horizontal_material_trimmed.xml b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml new file mode 100644 index 000000000000..95209f8eb8ab --- /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 000000000000..088e82bb4260 --- /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 000000000000..250188b892f4 --- /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 000000000000..b41762f7908e --- /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 000000000000..87d4aeaac84f --- /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 000000000000..5566ea3f62fc --- /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 000000000000..06bb779b91ef --- /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 000000000000..15dfcb70e25e --- /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 000000000000..3a09284d10a0 --- /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 000000000000..1d9dacd6c0f9 --- /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 000000000000..a38c3b4241a3 --- /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 000000000000..aec204f45aa7 --- /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 000000000000..1f0899623ddf --- /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 e4a96947aa6a..6a9254cad8f4 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 000000000000..b841419c1c75 --- /dev/null +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -0,0 +1,373 @@ +<?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:textColor="?android:attr/textColorPrimary" + android:fontFamily="google-sans" + 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:fontFamily="google-sans" + android:textSize="14sp"/> + </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:id="@+id/internet_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/mobile_network_layout" + android:layout_width="match_parent" + android:layout_height="88dp" + android:clickable="true" + android:focusable="true" + android:background="?android:attr/selectableItemBackground" + android:layout_gravity="center_vertical|start" + android:orientation="horizontal" + android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin" + android:layout_marginStart="@dimen/internet_dialog_network_layout_margin" + android:paddingStart="22dp" + android:paddingEnd="22dp"> + + <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:id="@+id/mobile_network_list" + 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" + android:textDirection="locale" + android:layout_marginStart="@dimen/internet_dialog_network_layout_margin" + android:layout_marginEnd="7dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:ellipsize="end" + android:textColor="?android:attr/textColorPrimary" + android:textSize="16sp" + android:fontFamily="google-sans"/> + <TextView + android:id="@+id/mobile_summary" + android:textDirection="locale" + android:layout_marginStart="@dimen/internet_dialog_network_layout_margin" + android:layout_marginEnd="34dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:ellipsize="end" + android:textColor="?android:attr/textColorTertiary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </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:switchMinWidth="@dimen/settingslib_switch_track_width" + android:layout_gravity="center" + android:layout_width="@dimen/settingslib_switch_track_width" + android:layout_height="@dimen/settingslib_switch_track_height" + 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" + android:layout_width="match_parent" + android:layout_height="72dp" + android:clickable="true" + android:focusable="true" + android:background="?android:attr/selectableItemBackground" + android:gravity="center" + android:orientation="horizontal" + android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin" + android:layout_marginStart="@dimen/internet_dialog_network_layout_margin" + android:paddingStart="22dp" + android:paddingEnd="22dp"> + + <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:textDirection="locale" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="start|center_vertical" + android:textColor="?android:attr/textColorPrimary" + android:textSize="16sp" + android:fontFamily="google-sans"/> + </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:switchMinWidth="@dimen/settingslib_switch_track_width" + android:layout_gravity="center" + android:layout_width="@dimen/settingslib_switch_track_width" + android:layout_height="@dimen/settingslib_switch_track_height" + 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" + android:layout_width="match_parent" + android:layout_height="72dp" + android:layout_gravity="center_vertical|start" + android:clickable="true" + android:focusable="true" + android:visibility="gone" + android:background="?android:attr/selectableItemBackground" + android:orientation="horizontal" + android:layout_marginStart="@dimen/internet_dialog_network_layout_margin" + android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin" + 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_connected_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + </FrameLayout> + + <LinearLayout + android:id="@+id/wifi_connected_list" + 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" + android:textDirection="locale" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="@dimen/internet_dialog_network_layout_margin" + android:ellipsize="end" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + <TextView + android:id="@+id/wifi_connected_summary" + android:textDirection="locale" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="@dimen/internet_dialog_network_layout_margin" + android:ellipsize="end" + android:textColor="?android:attr/textColorTertiary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </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:textDirection="locale" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="start|center_vertical" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </FrameLayout> + + </LinearLayout> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="48dp" + android:layout_marginBottom="40dp"> + <Button + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:id="@+id/done" + android:layout_width="67dp" + android:layout_height="36dp" + android:layout_marginEnd="24dp" + android:layout_gravity="end|center_vertical" + android:background="@drawable/internet_dialog_footer_background" + android:textColor="?android:attr/textColorPrimary" + android:text="@string/inline_done_button" + android:textSize="14sp" + android:fontFamily="google-sans"/> + </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 000000000000..b52933da818d --- /dev/null +++ b/packages/SystemUI/res/layout/internet_list_item.xml @@ -0,0 +1,95 @@ +<?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" + android:layout_width="match_parent" + android:layout_height="72dp" + android:layout_gravity="center_vertical|start" + android:clickable="true" + android:focusable="true" + android:background="?android:attr/selectableItemBackground" + android:orientation="horizontal" + android:paddingStart="20dp" + android:paddingEnd="40dp"> + <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/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_weight="1" + android:gravity="start|center_vertical" + android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"> + <TextView + android:id="@+id/wifi_title" + android:textDirection="locale" + android:layout_width="wrap_content" + android:layout_height="20dp" + android:gravity="start|center_vertical" + android:ellipsize="end" + android:textColor="?android:attr/textColorPrimary" + android:textSize="14sp" + android:fontFamily="google-sans" + android:layout_marginEnd="18dp"/> + <TextView + android:id="@+id/wifi_summary" + android:textDirection="locale" + android:layout_width="wrap_content" + android:layout_height="20dp" + android:gravity="start|center_vertical" + android:ellipsize="end" + android:textColor="?android:attr/textColorSecondary" + android:textSize="14sp" + android:fontFamily="google-sans" + android:layout_marginEnd="18dp"/> + </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_locked_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/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml index 5b9ca1b26158..966f9929b37c 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:minWidth="48dp" + 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: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 bff93a99258a..b1e8c386fe21 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/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index bea50e87a29a..71e8fc9991ed 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-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index da80b85b38bf..0a34dfd71c7e 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 b5337d363e12..3121ce37490a 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 2260d2175268..3b1f4fde109a 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> @@ -281,4 +281,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/config.xml b/packages/SystemUI/res/values/config.xml index d274c917c26d..b6d5b3a6760a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -194,6 +194,11 @@ low powered state yet. --> <bool name="doze_long_press_uses_prox">true</bool> + <!-- Doze: whether the brightness sensor uses the proximity sensor. + If both this parameter and doze_selectively_register_prox are true, registration for the + brightness sensor won't occur when the display state is ON. --> + <bool name="doze_brightness_uses_prox">true</bool> + <!-- Doze: should notifications be used as a pulse signal? --> <bool name="doze_pulse_on_notifications">true</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 78db2a8a3485..abdc98e4d4ac 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -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> @@ -1590,4 +1589,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 a67021611812..e38e069392c4 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] --> @@ -2992,4 +2994,37 @@ <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: Title to see all the networks [CHAR LIMIT=50] --> + <string name="see_all_networks">See all</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 51eabf60385e..d25474255bfc 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -903,4 +903,52 @@ <!-- 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> + </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 28a54d56b071..e115c342c4fe 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -132,8 +132,7 @@ public class KeyguardVisibilityHelper { .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 edb05691b530..dd3bb8990599 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.FrameLayout; import android.widget.ImageView; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; @@ -79,7 +80,11 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIcon.setImageDrawable(drawable); } - void setCenterLocation(@NonNull PointF center, int radius) { + /** + * 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 509ac8a6d9fe..2a4022ca57b6 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -46,6 +46,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 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; @@ -67,7 +68,8 @@ import javax.inject.Inject; /** * 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 { @@ -99,6 +101,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private CharSequence mUnlockedLabel; @NonNull private CharSequence mLockedLabel; @Nullable private final Vibrator mVibrator; + @Nullable private final AuthRippleController mAuthRippleController; private boolean mIsDozing; private boolean mIsBouncerShowing; @@ -135,7 +138,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull AccessibilityManager accessibilityManager, @NonNull ConfigurationController configurationController, @NonNull @Main DelayableExecutor executor, - @Nullable Vibrator vibrator + @Nullable Vibrator vibrator, + @Nullable AuthRippleController authRippleController ) { super(view); mStatusBarStateController = statusBarStateController; @@ -148,6 +152,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mConfigurationController = configurationController; mExecutor = executor; mVibrator = vibrator; + mAuthRippleController = authRippleController; final Context context = view.getContext(); mUnlockIcon = mView.getContext().getResources().getDrawable( @@ -168,15 +173,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override protected void onInit() { + mAuthController.addCallback(mAuthControllerCallback); + mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mView.setAccessibilityDelegate(mAccessibilityDelegate); } @Override protected void onViewAttached() { - // we check this here instead of onInit since the FingerprintManager + FaceManager may not - // have started up yet onInit - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; - updateConfiguration(); updateKeyguardShowing(); mUserUnlockedWithBiometric = false; @@ -497,8 +501,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme if (!wasClickableOnDownEvent()) { return; } + mDetectedLongPress = true; - if (mVibrator != null) { + if (onAffordanceClick() && mVibrator != null) { + // only vibrate if the click went through and wasn't intercepted by falsing mVibrator.vibrate( Process.myUid(), getContext().getOpPackageName(), @@ -506,8 +512,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme "lockIcon-onLongPress", VIBRATION_SONIFICATION_ATTRIBUTES); } - mDetectedLongPress = true; - onAffordanceClick(); } public boolean onSingleTapUp(MotionEvent e) { @@ -531,15 +535,24 @@ public class LockIconViewController extends ViewController<LockIconView> impleme return mDownDetected; } - private void onAffordanceClick() { + /** + * Whether we tried to launch the affordance. + * + * If falsing intercepts the click, returns false. + */ + private boolean onAffordanceClick() { if (mFalsingManager.isFalseTouch(LOCK_ICON)) { - return; + return false; } // pre-emptively set to true to hide view mIsBouncerShowing = true; + if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { + mAuthRippleController.showRipple(FINGERPRINT); + } updateVisibility(); mKeyguardViewController.showBouncer(/* scrim */ true); + return true; } }); @@ -577,4 +590,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme public void setAlpha(float alpha) { mView.setAlpha(alpha); } + + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + updateConfiguration(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 76f30a80114a..00b33a416df4 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.EnhancedEstimates; 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 @@ public class Dependency { @Inject Lazy<UiEventLogger> mUiEventLogger; @Inject Lazy<FeatureFlags> mFeatureFlagsLazy; @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy; + @Inject Lazy<InternetDialogFactory> mInternetDialogFactory; @Inject public Dependency() { @@ -578,6 +580,7 @@ public class Dependency { 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 a68f79604b25..c8f8404ff0d6 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.app.WallpaperColors; 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.ArraySet; 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 @@ public class ImageWallpaper extends WallpaperService { 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 @@ public class ImageWallpaper extends WallpaperService { 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) { @@ -124,13 +125,23 @@ public class ImageWallpaper extends WallpaperService { 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()); + } + + @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 +165,10 @@ public class ImageWallpaper extends WallpaperService { 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()); @@ -204,6 +199,8 @@ public class ImageWallpaper extends WallpaperService { @Override public void onDestroy() { + getDisplayContext().getSystemService(DisplayManager.class) + .unregisterDisplayListener(this); mMiniBitmap = null; mWorker.getThreadHandler().post(() -> { mRenderer.finish(); @@ -268,6 +265,16 @@ public class ImageWallpaper extends WallpaperService { * (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 +282,24 @@ public class ImageWallpaper extends WallpaperService { 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 +312,6 @@ public class ImageWallpaper extends WallpaperService { 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)) { diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index affad7a57d86..8c63f7bec23c 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -438,6 +438,12 @@ public class SwipeHelper implements Gefingerpoken { 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 81a13a236685..408201558a9b 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 class AssistOrbController { 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 c9e67715decb..5616a00592f2 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 @@ import com.android.systemui.people.widget.PeopleBackupHelper * 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/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 0ce1846e7745..ab5f2b8289be 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -786,7 +786,11 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, .build(sensorIds, credentialAllowed, mFpProps, mFaceProps); } - interface Callback { + /** + * AuthController callback used to receive signal for when biometric authenticators are + * registered. + */ + public interface Callback { /** * Called when authenticators are registered. If authenticators are already * registered before this call, this callback will never be triggered. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 2de489d2ce21..f22483aefe1e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -43,7 +43,6 @@ 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; @@ -76,6 +75,7 @@ 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.Optional; @@ -125,6 +125,7 @@ public class UdfpsController implements DozeReceiver { @Nullable private final UdfpsHbmProvider mHbmProvider; @NonNull private final KeyguardBypassController mKeyguardBypassController; @NonNull private final ConfigurationController mConfigurationController; + @NonNull private final SystemClock mSystemClock; @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. @@ -162,7 +163,8 @@ public class UdfpsController implements DozeReceiver { 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 = @@ -449,19 +451,19 @@ public class UdfpsController implements DozeReceiver { 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"); @@ -525,7 +527,8 @@ public class UdfpsController implements DozeReceiver { @NonNull KeyguardBypassController keyguardBypassController, @NonNull DisplayManager displayManager, @Main Handler mainHandler, - @NonNull ConfigurationController configurationController) { + @NonNull ConfigurationController configurationController, + @NonNull SystemClock systemClock) { mContext = context; mExecution = execution; // TODO (b/185124905): inject main handler and vibrator once done prototyping @@ -561,6 +564,7 @@ public class UdfpsController implements DozeReceiver { mainHandler); mKeyguardBypassController = keyguardBypassController; mConfigurationController = configurationController; + mSystemClock = systemClock; mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists @@ -703,15 +707,21 @@ public class UdfpsController implements DozeReceiver { 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) { @@ -781,6 +791,7 @@ public class UdfpsController implements DozeReceiver { mKeyguardViewMediator, mLockscreenShadeTransitionController, mConfigurationController, + mSystemClock, mKeyguardStateController, this ); @@ -817,10 +828,14 @@ public class UdfpsController implements DozeReceiver { 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/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 22d7a3ff44f0..679e27f456fc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 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; @@ -51,6 +52,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @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; @@ -61,7 +63,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud private int mStatusBarState; private float mTransitionToFullShadeProgress; private float mLastDozeAmount; - + private long mLastUdfpsBouncerShowTime = -1; private float mStatusBarExpansion; private boolean mLaunchTransitionFadingAway; @@ -84,6 +86,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull KeyguardViewMediator keyguardViewMediator, @NonNull LockscreenShadeTransitionController transitionController, @NonNull ConfigurationController configurationController, + @NonNull SystemClock systemClock, @NonNull KeyguardStateController keyguardStateController, @NonNull UdfpsController udfpsController) { super(view, statusBarStateController, statusBar, dumpManager); @@ -93,6 +96,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mKeyguardViewMediator = keyguardViewMediator; mLockScreenShadeTransitionController = transitionController; mConfigurationController = configurationController; + mSystemClock = systemClock; mKeyguardStateController = keyguardStateController; mUdfpsController = udfpsController; } @@ -103,6 +107,12 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } @Override + public void onInit() { + super.onInit(); + mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); + } + + @Override protected void onViewAttached() { super.onViewAttached(); final float dozeAmount = mStatusBarStateController.getDozeAmount(); @@ -171,6 +181,9 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud boolean udfpsAffordanceWasNotShowing = shouldPauseAuth(); mShowingUdfpsBouncer = show; if (mShowingUdfpsBouncer) { + mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis(); + } + if (mShowingUdfpsBouncer) { if (udfpsAffordanceWasNotShowing) { mView.animateInUdfpsBouncer(null); } @@ -238,16 +251,25 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud * 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. */ diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java index e1349f2aba6d..40c28fab51b8 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java @@ -21,6 +21,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; +import static com.android.systemui.classifier.Classifier.LOCK_ICON; import static com.android.systemui.classifier.Classifier.SHADE_DRAG; import android.graphics.Point; @@ -89,7 +90,9 @@ class ZigZagClassifier extends FalsingClassifier { Result calculateFalsingResult( @Classifier.InteractionType int interactionType, double historyBelief, double historyConfidence) { - if (interactionType == BRIGHTNESS_SLIDER || interactionType == SHADE_DRAG) { + if (interactionType == BRIGHTNESS_SLIDER + || interactionType == SHADE_DRAG + || interactionType == LOCK_ICON) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index c97a30e6e13e..79ee6a8e8830 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 @@ public class DependencyProvider { @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/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 470d2f364c1c..98d2739836a9 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -31,6 +31,7 @@ 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; @@ -63,6 +64,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi 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; @@ -87,7 +89,8 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, WakefulnessLifecycle wakefulnessLifecycle, - DozeParameters dozeParameters) { + DozeParameters dozeParameters, + DockManager dockManager) { mContext = context; mDozeService = service; mSensorManager = sensorManager; @@ -96,6 +99,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi mDozeParameters = dozeParameters; mDozeHost = host; mHandler = handler; + mDockManager = dockManager; mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness; mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness; @@ -122,13 +126,20 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi @Override public void onScreenState(int state) { - if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) { + boolean isDockedScreenOn = state == Display.STATE_ON && mDockManager.isDocked(); + if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND + || (isDockedScreenOn && shouldRegisterLightSensorWhenScreenOnDocked())) { setLightSensorEnabled(true); } else { setLightSensorEnabled(false); } } + private boolean shouldRegisterLightSensorWhenScreenOnDocked() { + return !mDozeParameters.brightnessUsesProx() + || !mDozeParameters.getSelectivelyRegisterSensorsUsingProx(); + } + 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 b2db86f16104..55e6154b829d 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 class DozeSensors { 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/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index bfa478088cad..5b327bd5e4c1 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 @@ import javax.inject.Inject * 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 28f63b07e584..6561bd5a5323 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 @@ import javax.inject.Inject * 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 bc4ced452630..1b4a47e4bf39 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.logging.UiEventLogger; 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 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite SysUiState sysUiState, @Main Handler handler, PackageManager packageManager, - StatusBar statusBar) { + StatusBar statusBar, + KeyguardUpdateMonitor keyguardUpdateMonitor) { super(context, windowManagerFuncs, @@ -204,7 +206,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite sysUiState, handler, packageManager, - statusBar); + statusBar, + keyguardUpdateMonitor); mLockPatternUtils = lockPatternUtils; mKeyguardStateController = keyguardStateController; @@ -266,7 +269,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite this::getWalletViewController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(), - getStatusBar()); + getStatusBar(), getKeyguardUpdateMonitor(), mLockPatternUtils); if (shouldShowLockMessage(dialog)) { dialog.showLockMessage(); @@ -334,12 +337,13 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite 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 06e74821869e..9ada54b9ef6f 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.Window; 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.statusbar.IStatusBarService; 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene SysUiState sysUiState, @Main Handler handler, PackageManager packageManager, - StatusBar statusBar) { + StatusBar statusBar, + KeyguardUpdateMonitor keyguardUpdateMonitor) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -369,6 +378,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mMainHandler = handler; mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; mStatusBar = statusBar; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -422,6 +432,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return mStatusBar; } + protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() { + return mKeyguardUpdateMonitor; + } + /** * Show the global actions dialog (creating if necessary) * @@ -653,7 +667,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private GlobalActionsInfoProvider mInfoProvider; private GestureDetector mGestureDetector; private StatusBar mStatusBar; + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private LockPatternUtils mLockPatternUtils; protected ViewGroup mContainer; @@ -2173,7 +2189,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mUiEventLogger = uiEventLogger; mInfoProvider = infoProvider; mStatusBar = statusBar; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLockPatternUtils = lockPatternUtils; mGestureDetector = new GestureDetector(mContext, mGestureListener); @@ -2308,6 +2327,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java deleted file mode 100644 index 38b20ee45946..000000000000 --- 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 000000000000..7ffa9d931ff0 --- /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/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index 000fd1c4bd2e..9f585bdfaeb0 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 @@ public interface QSHost { 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/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 541ee2c4fe8f..d5349d378305 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -347,6 +347,17 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D 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); @@ -368,6 +379,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D * @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; @@ -382,6 +394,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } 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 77906abce625..c88c198f767c 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.StatusBarIconController.TintedIconMa 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 @@ public class QuickStatusBarHeader extends FrameLayout { 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 @@ public class QuickStatusBarHeader extends FrameLayout { 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 @@ public class QuickStatusBarHeader extends FrameLayout { 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 @@ public class QuickStatusBarHeader extends FrameLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) { mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight(); - updateAnimators(); + post(this::updateAnimators); } } @@ -186,6 +195,8 @@ public class QuickStatusBarHeader extends FrameLayout { super.onConfigurationChanged(newConfig); updateResources(); setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE); + setSecurityHeaderContainerVisibility( + newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE); } @Override @@ -206,6 +217,10 @@ public class QuickStatusBarHeader extends FrameLayout { mPrivacyContainer.setLayoutParams(lp); } + private void setSecurityHeaderContainerVisibility(boolean landscape) { + mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE); + } + private void updateBatteryMode() { if (mConfigShowBatteryEstimate && !mHasCenterCutout) { mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE); @@ -280,7 +295,8 @@ public class QuickStatusBarHeader extends FrameLayout { 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 +305,14 @@ public class QuickStatusBarHeader extends FrameLayout { 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 +322,7 @@ public class QuickStatusBarHeader extends FrameLayout { @Override public void onAnimationAtStart() { super.onAnimationAtStart(); + mClockDateView.setFreezeSwitching(false); setSeparatorVisibility(mShowClockIconsSeparator); // In QQS we never ignore RSSI. mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots); @@ -434,10 +455,11 @@ public class QuickStatusBarHeader extends FrameLayout { 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 da75c9e45c54..18d6e646b007 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.FeatureFlags; 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader SysuiColorExtractor colorExtractor, PrivacyDialogController privacyDialogController, QSExpansionPathInterpolator qsExpansionPathInterpolator, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + VariableDateViewController.Factory variableDateViewControllerFactory) { super(view); mPrivacyItemController = privacyItemController; mActivityStarter = activityStarter; @@ -154,6 +159,12 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader 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 2e771d6fb669..b1cd03c4a2f2 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 @@ public class QSIconViewImpl extends QSIconView { : 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/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 4b13015361cc..04f089d31664 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 @@ public class CastTile extends QSTileImpl<BooleanState> { } 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 @@ public class CastTile extends QSTileImpl<BooleanState> { } } + // 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 @@ public class CastTile extends QSTileImpl<BooleanState> { 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/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 7cb1421e3f0f..cc9e7485dcff 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.AlphaControlledSignalTileView; 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 @@ import javax.inject.Inject; /** 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 @@ public class InternetTile extends QSTileImpl<SignalState> { 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 @@ public class InternetTile extends QSTileImpl<SignalState> { @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 @@ public class InternetTile extends QSTileImpl<SignalState> { 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 @@ public class InternetTile extends QSTileImpl<SignalState> { 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/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java new file mode 100644 index 000000000000..4e897d9a8667 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -0,0 +1,202 @@ +/* + * 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.wifitrackerlib.WifiEntry.SECURITY_NONE; +import static com.android.wifitrackerlib.WifiEntry.SECURITY_OWE; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.text.Html; +import android.text.TextUtils; +import android.view.Gravity; +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.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 mWifiLockedIcon; + final Context mContext; + final InternetDialogController mInternetDialogController; + + 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); + mWifiLockedIcon = view.requireViewById(R.id.wifi_locked_icon); + mWifiIconInjector = mInternetDialogController.getWifiIconInjector(); + } + + void onBind(WifiEntry wifiEntry) { + int security = wifiEntry.getSecurity(); + try { + mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry)); + if (isOpenNetwork(security)) { + mWifiLockedIcon.setVisibility(View.GONE); + } else { + mWifiLockedIcon.setVisibility(View.VISIBLE); + mWifiLockedIcon.setImageDrawable( + mContext.getDrawable(R.drawable.ic_friction_lock_closed)); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + + setWifiNetworkLayout(wifiEntry.getTitle(), + Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY)); + + 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); + }); + } + + /** Return true if this is an open network AccessPoint. */ + boolean isOpenNetwork(int security) { + return security == SECURITY_NONE + || security == SECURITY_OWE; + } + + void setWifiNetworkLayout(CharSequence title, CharSequence summary) { + mWifiNetworkLayout.setVisibility(View.VISIBLE); + mWifiTitleText.setText(title); + if (TextUtils.isEmpty(summary)) { + mWifiSummaryText.setVisibility(View.GONE); + return; + } else { + mWifiSummaryText.setVisibility(View.VISIBLE); + } + mWifiSummaryText.setText(summary); + } + + Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) throws Throwable { + 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(); + } + } +} 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 000000000000..4d0cbd15a788 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -0,0 +1,599 @@ +/* + * 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.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.Switch; +import android.widget.TextView; + +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.settingslib.Utils; +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 mInternetListLayout; + private LinearLayout mConnectedWifListLayout; + private LinearLayout mConnectedWifList; + private LinearLayout mMobileNetworkLayout; + private LinearLayout mMobileNetworkList; + private LinearLayout mTurnWifiOnLayout; + 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 Button mDoneButton; + private Drawable mBackgroundOn; + private int mListMaxHeight; + private int mLayoutWidth; + 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); + mLayoutWidth = context.getResources().getDimensionPixelSize( + R.dimen.internet_dialog_list_max_width); + 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); + mInternetListLayout = mDialogView.requireViewById(R.id.internet_list); + mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout); + mMobileNetworkList = mDialogView.requireViewById(R.id.mobile_network_list); + 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); + mConnectedWifList = mDialogView.requireViewById(R.id.wifi_connected_list); + 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); + mDoneButton = mDialogView.requireViewById(R.id.done); + 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); + mDoneButton.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()); + } + setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()); + + 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); + }); + mDoneButton.setOnClickListener(v -> dismiss()); + } + + private void setMobileDataLayout(boolean isCellularNetwork) { + 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()); + if (mInternetDialogController.isNightMode()) { + int titleColor = isCellularNetwork ? mContext.getColor( + R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorPrimary); + int summaryColor = isCellularNetwork ? mContext.getColor( + R.color.connected_network_secondary_color) : Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorSecondary); + + mMobileTitleText.setTextColor(titleColor); + mMobileSummaryText.setTextColor(summaryColor); + } + mMobileNetworkLayout.setBackground(isCellularNetwork ? mBackgroundOn : null); + + mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); + } + } + + private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) { + mWiFiToggle.setChecked(isWifiEnabled); + if (isDeviceLocked && mInternetDialogController.isNightMode()) { + int titleColor = mConnectedWifiEntry != null ? mContext.getColor( + R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorPrimary); + mWifiToggleTitleText.setTextColor(titleColor); + } + mTurnWifiOnLayout.setBackground( + (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null); + } + + private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) { + if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) { + mConnectedWifListLayout.setBackground(null); + mConnectedWifListLayout.setVisibility(View.GONE); + return; + } + mConnectedWifListLayout.setVisibility(View.VISIBLE); + mConnectedWifiTitleText.setText(mConnectedWifiEntry.getTitle()); + mConnectedWifiSummaryText.setText(mConnectedWifiEntry.getSummary(false)); + mConnectedWifiIcon.setImageDrawable( + mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry)); + if (mInternetDialogController.isNightMode()) { + mConnectedWifiTitleText.setTextColor( + mContext.getColor(R.color.connected_network_primary_color)); + mConnectedWifiSummaryText.setTextColor( + mContext.getColor(R.color.connected_network_secondary_color)); + } + mWifiSettingsIcon.setColorFilter( + mContext.getColor(R.color.connected_network_primary_color)); + mConnectedWifListLayout.setBackground(mBackgroundOn); + } + + void onClickConnectedWifi() { + mInternetDialogController.launchWifiNetworkDetailsSetting(); + } + + 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 = + mSubscriptionManager.getDefaultDataSubscriptionInfo().getCarrierName(); + 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 + public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { + 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 000000000000..62fa3d4ac819 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -0,0 +1,940 @@ +/* + * 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.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.graphics.Color; +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.NetworkRequest; +import android.net.wifi.WifiManager; +import android.os.Handler; +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.widget.Toast; + +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.Utils; +import com.android.settingslib.graph.SignalDrawable; +import com.android.settingslib.mobile.MobileMappings; +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.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.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 TelephonyDisplayInfo mTelephonyDisplayInfo = + new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE); + private Handler mHandler; + 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; + + @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; + + 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) { + if (DEBUG) { + Log.d(TAG, "Init InternetDialogController"); + } + mHandler = handler; + mExecutor = mainExecutor; + mContext = context; + mGlobalSettings = globalSettings; + mWifiManager = wifiManager; + mTelephonyManager = telephonyManager; + mConnectivityManager = connectivityManager; + mSubscriptionManager = subscriptionManager; + mBroadcastDispatcher = broadcastDispatcher; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mKeyguardStateController = keyguardStateController; + mConnectionStateFilter = new IntentFilter(); + mConnectionStateFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); + mUiEventLogger = uiEventLogger; + mActivityStarter = starter; + mAccessPointController = accessPointController; + mConfig = MobileMappings.Config.readConfig(mContext); + mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext); + } + + 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); + } + mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mInternetTelephonyCallback = new InternetTelephonyCallback(); + mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback); + // Listen the connectivity changes + mConnectivityManager.registerNetworkCallback(new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(), new DataConnectivityListener(), mHandler); + 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); + } + + @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 = mConnectedEntry == null ? null : mConnectedEntry.getKey(); + 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) { + 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; + } + + boolean isNightMode() { + return (mContext.getResources().getConfiguration().uiMode + & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + } + + 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(); + } + + drawable.setTint(activeNetworkIsCellular() ? mContext.getColor( + R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorTertiary)); + } 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, mTelephonyManager, 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; + return resId != 0 + ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : ""; + } + + private String getMobileSummary(Context context, TelephonyManager telephonyManager, + 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()) { + 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() { + Intent intent = getWifiDetailsSettingsIntent(); + 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 */); + } + } + + 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); + } + } + } + } + } + + 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, mContext, ap)); + return false; + } + + static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback { + final ActivityStarter mActivityStarter; + final Context mContext; + final WifiEntry mWifiEntry; + + WifiEntryConnectCallback(ActivityStarter activityStarter, Context context, + WifiEntry connectWifiEntry) { + mActivityStarter = activityStarter; + mContext = context; + mWifiEntry = connectWifiEntry; + } + + @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, true); + } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) { + Toast.makeText(mContext, R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + } 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 (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 networkCapabilities) { + if (mCanConfigWifi) { + for (int transport : networkCapabilities.getTransportTypes()) { + if (transport == NetworkCapabilities.TRANSPORT_WIFI) { + scanWifiAccessPoints(); + break; + } + } + } + final Network activeNetwork = mConnectivityManager.getActiveNetwork(); + if (activeNetwork != null && activeNetwork.equals(network)) { + // update UI + mCallback.onCapabilitiesChanged(network, networkCapabilities); + } + } + } + + private final BroadcastReceiver mConnectionStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { + if (DEBUG) { + Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"); + } + 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 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); + } +} 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 000000000000..11c6980678b1 --- /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 000000000000..6aaba997faad --- /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/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 0eaef72ae29b..31d51f1d1a60 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 @@ public class LongScreenshotActivity extends Activity { @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 @@ public class LongScreenshotActivity extends Activity { } 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 @@ public class LongScreenshotActivity extends Activity { 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 16872b08b9c8..3fbdc23f55d0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -559,8 +559,8 @@ public class ScreenshotController { mScreenshotView.reset(); } - mScreenshotView.updateOrientation(mWindowManager.getCurrentWindowMetrics() - .getWindowInsets().getDisplayCutout()); + mScreenshotView.updateOrientation( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); mScreenBitmap = screenshot; @@ -594,9 +594,8 @@ public class ScreenshotController { // 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 +659,7 @@ public class ScreenshotController { + mLastScrollCaptureResponse.getWindowTitle() + "]"); final ScrollCaptureResponse response = mLastScrollCaptureResponse; - mScreenshotView.showScrollChip(/* onClick */ () -> { + mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> { DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); Bitmap newScreenshot = captureScreenshot( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java index 5cf018813133..169b28c6b373 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 @@ public enum ScreenshotEvent implements UiEventLogger.UiEventEnum { @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 e9e62f26a10e..dfb39e300450 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 @@ public class ScreenshotView extends FrameLayout implements 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 @@ public class ScreenshotView extends FrameLayout implements /** * 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 @@ public class ScreenshotView extends FrameLayout implements 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 @@ public class ScreenshotView extends FrameLayout implements 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 @@ public class ScreenshotView extends FrameLayout implements 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 @@ public class ScreenshotView extends FrameLayout implements mSmartChips.clear(); mQuickShareChip = null; setAlpha(1); - mDismissButton.setTranslationY(0); - mActionsContainer.setTranslationY(0); - mActionsContainerBackground.setTranslationY(0); mScreenshotSelectorView.stop(); } @@ -1024,22 +1031,19 @@ public class ScreenshotView extends FrameLayout implements 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 6dc68746e3ec..ef7355a09fbf 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; 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 @@ public class ScrollCaptureController { 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 @@ public class ScrollCaptureController { 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 @@ public class ScrollCaptureController { @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 @@ public class ScrollCaptureController { 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 @@ public class ScrollCaptureController { 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 @@ public class ScrollCaptureController { 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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 503b5c0ee4b0..eb5f82ca417e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -811,17 +811,12 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState); } } else if (mKeyguardUpdateMonitor.isScreenOn()) { - if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { - showTransientIndication(mContext.getString(R.string.keyguard_unlock_press), - false /* isError */, true /* hideOnScreenOff */); - } else { - showTransientIndication(mContext.getString(R.string.keyguard_unlock), - false /* isError */, true /* hideOnScreenOff */); - } + 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 @@ -833,6 +828,11 @@ public class KeyguardIndicationController { } 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) { @@ -913,7 +913,7 @@ public class KeyguardIndicationController { } else if (mKeyguardUpdateMonitor.isScreenOn()) { if (biometricSourceType == BiometricSourceType.FACE && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) { - showTryFingerprintMsg(); + showTryFingerprintMsg(helpString); return; } showTransientIndication(helpString, false /* isError */, showActionToUnlock); @@ -933,7 +933,7 @@ public class KeyguardIndicationController { && shouldSuppressFaceMsgAndShowTryFingerprintMsg() && !mStatusBarKeyguardViewManager.isBouncerShowing() && mKeyguardUpdateMonitor.isScreenOn()) { - showTryFingerprintMsg(); + showTryFingerprintMsg(errString); return; } if (msgId == FaceManager.FACE_ERROR_TIMEOUT) { @@ -942,7 +942,7 @@ public class KeyguardIndicationController { 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 538db6168408..21ed9da896a5 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 PowerButtonReveal( */ 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 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, field = value revealEffect.setRevealAmountOnScrim(value, this) - revealAmountListener.accept(value) + updateScrimOpaque() invalidate() } } @@ -201,6 +204,31 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } /** + * 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 b8334272c157..e8458040f448 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -181,7 +181,8 @@ class NotificationShadeDepthController @Inject constructor( 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) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index f0d779ce1e0f..6ea79af8b9ad 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 @@ public interface NotificationShadeWindowController extends RemoteInputController 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 4a467ce3c987..d01fc93ee84c 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 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context // 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/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 86c90c7bcb2e..9eb95c409009 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import android.annotation.ColorInt; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -28,15 +27,12 @@ import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ViewState; public class FooterView extends StackScrollerDecorView { - private final int mClearAllTopPadding; private FooterViewButton mDismissButton; private FooterViewButton mManageButton; private boolean mShowHistory; public FooterView(Context context, AttributeSet attrs) { super(context, attrs); - mClearAllTopPadding = context.getResources().getDimensionPixelSize( - R.dimen.clear_all_padding_top); } @Override @@ -55,11 +51,6 @@ public class FooterView extends StackScrollerDecorView { mManageButton = findViewById(R.id.manage_text); } - public void setTextColor(@ColorInt int color) { - mManageButton.setTextColor(color); - mDismissButton.setTextColor(color); - } - public void setManageButtonClickListener(OnClickListener listener) { mManageButton.setOnClickListener(listener); } @@ -95,21 +86,25 @@ public class FooterView extends StackScrollerDecorView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - int textColor = getResources().getColor(R.color.notif_pill_text); - Resources.Theme theme = getContext().getTheme(); - mDismissButton.setBackground( - getResources().getDrawable(R.drawable.notif_footer_btn_background, theme)); - mDismissButton.setTextColor(textColor); - mManageButton.setBackground( - getResources().getDrawable(R.drawable.notif_footer_btn_background, theme)); - mManageButton = findViewById(R.id.manage_text); + updateColors(); mDismissButton.setText(R.string.clear_all_notifications_text); - mManageButton.setTextColor(textColor); mDismissButton.setContentDescription( mContext.getString(R.string.accessibility_clear_all)); showHistory(mShowHistory); } + /** + * Update the text and background colors for the current color palette and night mode setting. + */ + public void updateColors() { + Resources.Theme theme = mContext.getTheme(); + int textColor = getResources().getColor(R.color.notif_pill_text, theme); + mDismissButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + mDismissButton.setTextColor(textColor); + mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + mManageButton.setTextColor(textColor); + } + @Override public ExpandableViewState createExpandableViewState() { return new FooterViewState(); 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 dba3401cc28e..9c755e970a0f 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 @@ public abstract class StackScrollerDecorView extends ExpandableView { } /** - * 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 @@ public abstract class StackScrollerDecorView extends ExpandableView { 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 @@ public abstract class StackScrollerDecorView extends ExpandableView { * @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 @@ public abstract class StackScrollerDecorView extends ExpandableView { } 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 23aefd9bfd8e..594afceab63a 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 @@ public class NotificationRoundnessManager { private Runnable mRoundingChangedCallback; private ExpandableNotificationRow mTrackedHeadsUp; private float mAppearFraction; + private boolean mIsDismissAllInProgress; private ExpandableView mSwipedView = null; private ExpandableView mViewBeforeSwipedView = null; @@ -162,6 +163,10 @@ public class NotificationRoundnessManager { } } + void setDismissAllInProgress(boolean isClearingAll) { + mIsDismissAllInProgress = isClearingAll; + } + private float getRoundness(ExpandableView view, boolean top) { if (view == null) { return 0f; @@ -171,6 +176,11 @@ public class NotificationRoundnessManager { || 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 289c32f17b31..a771274e3433 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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; @@ -259,7 +263,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mDismissAllInProgress; - private boolean mFadeNotificationsOnDismiss; private FooterDismissListener mFooterDismissListener; private boolean mFlingAfterUpEvent; @@ -1192,7 +1195,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void clampScrollPosition() { int scrollRange = getScrollRange(); - if (scrollRange < mOwnScrollY) { + if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) { boolean animateStackY = false; if (scrollRange < getScrollAmountToScrollBoundary() && mAnimateStackYForContentHeightChange) { @@ -1708,6 +1711,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @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 */); } @@ -4050,6 +4058,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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) @@ -4231,7 +4252,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); mSectionsManager.setHeaderForegroundColor(textColor); - mFooterView.setTextColor(textColor); + mFooterView.updateColors(); mEmptyShadeView.setTextColor(textColor); } @@ -4407,6 +4428,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void setDismissAllInProgress(boolean dismissAllInProgress) { mDismissAllInProgress = dismissAllInProgress; mAmbientState.setDismissAllInProgress(dismissAllInProgress); + mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress); handleDismissAllClipping(); } @@ -4947,129 +4969,137 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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; + } - if (mDismissListener != null) { - mDismissListener.onDismiss(selection); - } + 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 (viewsToRemove.isEmpty()) { - if (closeShade && mShadeController != null) { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + if (shouldHideParent(view, selection)) { + viewsToHide.add(view); } - return; - } + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - performDismissAllAnimations( - viewsToHide, - closeShade, - () -> onDismissAllAnimationsEnd(viewsToRemove, selection)); + if (isChildrenVisible(parent)) { + for (ExpandableNotificationRow child : parent.getAttachedChildren()) { + if (isVisible(child) && includeChildInDismissAll(child, selection)) { + viewsToHide.add(child); + } + } + } + } + } + return viewsToHide; } - private boolean includeChildInDismissAll( - ExpandableNotificationRow row, + private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend( @SelectedRows int selection) { - return canChildBeDismissed(row) && matchesSelection(row, 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; } /** - * 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. + * 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) - 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(); - } + @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); + } + final Runnable dismissInBackend = () -> { + onDismissAllAnimationsEnd(rowsToDismissInBackend, selection); }; - - if (hideAnimatedList.isEmpty()) { - onSlideAwayAnimationComplete.run(); + if (viewsToAnimateAway.isEmpty()) { + dismissInBackend.run(); return; } - - // let's disable our normal animations + // Disable normal animations setDismissAllInProgress(true); + mShadeNeedsToClose = closeShade; // 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(); + 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 = hideAnimatedList.get(i); + View view = viewsToAnimateAway.get(i); Runnable endRunnable = null; if (i == 0) { - endRunnable = onSlideAwayAnimationComplete; + endRunnable = dismissInBackend; } dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE); - currentDelay = Math.max(50, currentDelay - rowDelayDecrement); + currentDelay = Math.max(30, currentDelay - rowDelayDecrement); totalDelay += currentDelay; } } + private boolean includeChildInDismissAll( + ExpandableNotificationRow row, + @SelectedRows int selection) { + return canChildBeDismissed(row) && matchesSelection(row, selection); + } + public void setNotificationActivityStarter( NotificationActivityStarter notificationActivityStarter) { mNotificationActivityStarter = notificationActivityStarter; @@ -5085,6 +5115,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 9e4adce47e0c..faba48f82cb3 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 @@ -1122,6 +1122,10 @@ public class NotificationStackScrollLayoutController { mZenModeController.areNotificationsHiddenInShade()); } + public boolean areNotificationsHiddenInShade() { + return mZenModeController.areNotificationsHiddenInShade(); + } + public boolean isShowingEmptyShadeView() { return mShowEmptyShadeView; } @@ -1170,6 +1174,10 @@ public class NotificationStackScrollLayoutController { * 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; } @@ -1180,8 +1188,11 @@ public class NotificationStackScrollLayoutController { continue; } final ExpandableNotificationRow row = (ExpandableNotificationRow) child; - if (row.canViewBeDismissed() && - NotificationStackScrollLayout.matchesSelection(row, selection)) { + final boolean matchClearable = + isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed(); + final boolean inSection = + NotificationStackScrollLayout.matchesSelection(row, selection); + if (matchClearable && inSection) { return true; } } 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 2c810c93b2ee..8be5de7ae56e 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 @@ public class StackScrollAlgorithm { 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 @@ public class StackScrollAlgorithm { + 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 @@ public class StackScrollAlgorithm { } } } - // 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 ee12b4b2d728..4466cfe99fe1 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 class StackStateAnimator { 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 5a6db213d87f..36f6c4fd57a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -268,6 +268,13 @@ public class DozeParameters implements TunerService.Tunable, } /** + * Whether the brightness sensor uses the proximity sensor. + */ + public boolean brightnessUsesProx() { + return mResources.getBoolean(R.bool.doze_brightness_uses_prox); + } + + /** * Callback to listen for DozeParameter changes. */ public void addCallback(Callback callback) { @@ -303,6 +310,7 @@ public class DozeParameters implements TunerService.Tunable, pw.print("getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold()); pw.print("getSelectivelyRegisterSensorsUsingProx(): "); pw.println(getSelectivelyRegisterSensorsUsingProx()); + pw.print("brightnessUsesProx(): "); pw.println(brightnessUsesProx()); } interface Callback { 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 0a4e59c0391e..4701d8b32ee6 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL 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/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 246810a2d70b..c26782b017c6 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 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } @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 b5d9bd67bd2d..e57e2005e3b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -311,6 +311,12 @@ public class NotificationShadeWindowViewController { // 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() @@ -338,6 +344,12 @@ public class NotificationShadeWindowViewController { 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/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 323a1128d3bb..de0f31d3cf59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -1211,10 +1211,14 @@ public abstract class PanelViewController { 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 +1231,7 @@ public abstract class PanelViewController { 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/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 7d25aeef6f98..43ec6e6e87d0 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (isNaN(expansionFraction)) { return; } + expansionFraction = Interpolators + .getNotificationScrimAlpha(expansionFraction, false /* notification */); boolean qsBottomVisible = qsPanelBottomY > 0; if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) { mQsExpansion = expansionFraction; 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 2c0de629de8a..15b8c67fd4ba 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 @@ public enum ScrimState { BUBBLE_EXPANDED { @Override public void prepare(ScrimState previousState) { + mBehindAlpha = mClipQsScrim ? 1 : 0; + mNotifAlpha = 0; + mFrontAlpha = 0; + + mAnimationDuration = mKeyguardFadingAway + ? mKeyguardFadingAwayDuration + : StatusBar.FADE_KEYGUARD_DURATION; + + mAnimateChange = !mLaunchingAffordanceWithPreview; + mFrontTint = Color.TRANSPARENT; - mBehindTint = Color.TRANSPARENT; + mBehindTint = Color.BLACK; mBubbleTint = Color.BLACK; + mBlankScreen = false; - mFrontAlpha = 0f; - mBehindAlpha = mDefaultScrimAlpha; + 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 4705a362b669..d7a359e9b4d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1044,7 +1044,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationShadeWindowViewController, mNotificationPanelViewController, mAmbientIndicationContainer); - mDozeParameters.addCallback(this::updateLightRevealScrimVisibility); + updateLightRevealScrimVisibility(); mConfigurationController.addCallback(this); @@ -1261,8 +1261,19 @@ public class StatusBar extends SystemUI implements DemoMode, 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(); @@ -4956,11 +4967,5 @@ public class StatusBar extends SystemUI implements DemoMode, } 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 8a7708aaa8c2..3653b95351b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -303,8 +303,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * 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() { 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 47deb1f0084b..8821de077ec3 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 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, 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 6b52dca42eda..f8120a8a7029 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -5,20 +5,23 @@ import android.animation.AnimatorListenerAdapter 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 com.android.systemui.statusbar.StatusBarState import android.view.View import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.LightRevealScrim -import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.StatusBarStateControllerImpl import com.android.systemui.statusbar.notification.AnimatableProperty import com.android.systemui.statusbar.notification.PropertyAnimator 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,13 +49,16 @@ class UnlockedScreenOffAnimationController @Inject constructor( 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 @@ -79,6 +85,12 @@ class UnlockedScreenOffAnimationController @Inject constructor( }) } + val animatorDurationScaleObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + updateAnimatorDurationScale() + } + } + fun initialize( statusBar: StatusBar, lightRevealScrim: LightRevealScrim @@ -86,14 +98,25 @@ class UnlockedScreenOffAnimationController @Inject constructor( 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 +161,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // Waking up, so reset this flag. decidedToAnimateGoingToSleep = null + shouldAnimateInKeyguard = false lightRevealAnimator.cancel() handler.removeCallbacksAndMessages(null) } @@ -146,7 +170,6 @@ class UnlockedScreenOffAnimationController @Inject constructor( // 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 +190,15 @@ class UnlockedScreenOffAnimationController @Inject constructor( 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 } @@ -228,6 +251,10 @@ class UnlockedScreenOffAnimationController @Inject constructor( 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 +262,4 @@ class UnlockedScreenOffAnimationController @Inject constructor( fun isScreenOffLightRevealAnimationPlaying(): Boolean { return lightRevealAnimationPlaying } -}
\ No newline at end of file +} 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 ab58286859cc..6d6320e6962d 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.SysUISingleton; 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 @@ public class AccessPointControllerImpl 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 @@ public class AccessPointControllerImpl WifiPickerTrackerFactory wifiPickerTrackerFactory ) { mUserManager = userManager; + mUserTracker = userTracker; mCurrentUser = userTracker.getUserId(); mMainExecutor = mainExecutor; mWifiPickerTrackerFactory = wifiPickerTrackerFactory; @@ -118,6 +121,11 @@ public class AccessPointControllerImpl 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 @@ public class AccessPointControllerImpl } @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/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index ef2ca985858d..eeea699a0b74 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 android.telephony.SubscriptionInfo; 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 @@ public interface NetworkController extends CallbackController<SignalCallback>, D 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 c49de7ab0e5d..af0d41331c28 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.R; 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 @@ public class NetworkControllerImpl extends BroadcastReceiver 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 @@ public class NetworkControllerImpl extends BroadcastReceiver DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags, - DumpManager dumpManager) { + DumpManager dumpManager, + @Main Handler handler, + InternetDialogFactory internetDialogFactory) { this(context, connectivityManager, telephonyManager, telephonyListenerManager, @@ -242,6 +249,8 @@ public class NetworkControllerImpl extends BroadcastReceiver featureFlags, dumpManager); mReceiverHandler.post(mRegisterListeners); + mMainHandler = handler; + mInternetDialogFactory = internetDialogFactory; } @VisibleForTesting @@ -480,6 +489,9 @@ public class NetworkControllerImpl extends BroadcastReceiver 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 @@ public class NetworkControllerImpl extends BroadcastReceiver 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/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 84d7c05ddc14..5d7d4809dd57 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.MetricsLogger; 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 41b1dd12639a..4e33529f3c36 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 @@ public class SmartReplyView extends ViewGroup { 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 000000000000..ae9d9ee445f2 --- /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 000000000000..99d84c4d0ced --- /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 c3b4fbe9a13d..fe0b97056024 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 @@ public class ThemeOverlayApplier implements Dumpable { /* 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,13 @@ public class ThemeOverlayApplier implements Dumpable { * 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(() -> { + Set<UserHandle> managedProfiles, + Runnable onOverlaysApplied) { + mBgExecutor.execute(() -> { // Disable all overlays that have not been specified in the user setting. final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); @@ -221,6 +226,7 @@ public class ThemeOverlayApplier implements Dumpable { try { mOverlayManager.commit(transaction.build()); + mMainExecutor.execute(onOverlaysApplied); } catch (SecurityException | IllegalStateException e) { Log.e(TAG, "setEnabled failed", e); } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 81999b534046..c3327dfc6d4e 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -521,17 +521,21 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { .map(key -> key + " -> " + categoryToPackage.get(key)).collect( Collectors.joining(", "))); } + Runnable overlaysAppliedRunnable = () -> onOverlaysApplied(); if (mNeedsOverlayCreation) { mNeedsOverlayCreation = false; mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { mSecondaryOverlay, mNeutralOverlay - }, currentUser, managedProfiles); + }, currentUser, managedProfiles, overlaysAppliedRunnable); } else { mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser, - managedProfiles); + managedProfiles, overlaysAppliedRunnable); } } + protected void onOverlaysApplied() { + } + @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("mSystemColors=" + mCurrentColors); diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index 98b4209ede00..bfa50bcee270 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.os.UserHandle; 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 class UsbPermissionActivity extends AlertActivity 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/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java index 90e022a52d7a..bd1103982017 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 @@ public class ProximitySensor implements ThresholdSensor { && (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 @@ public class ProximitySensor implements ThresholdSensor { * 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 @@ public class ProximitySensor implements ThresholdSensor { 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 @@ public class ProximitySensor implements ThresholdSensor { } if (!mSecondarySafe && !event.getBelow()) { - mSecondaryThresholdSensor.pause(); + chooseSensor(); } mLastEvent = event; diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index f1c687ff3224..2c9c98032245 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.lifecycle.ProcessLifecycleOwnerInitializer" tools:replace="android:authorities" 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 2120b0ee4790..cff2aed3f8fc 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; @@ -69,6 +71,7 @@ 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; @@ -147,6 +150,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private Handler mHandler; @Mock private ConfigurationController mConfigurationController; + @Mock + private SystemClock mSystemClock; private FakeExecutor mFgExecutor; @@ -229,7 +234,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mKeyguardBypassController, mDisplayManager, mHandler, - mConfigurationController); + mConfigurationController, + mSystemClock); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -413,6 +419,21 @@ public class UdfpsControllerTest extends SysuiTestCase { } @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); @@ -544,5 +565,10 @@ public class UdfpsControllerTest extends SysuiTestCase { 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 5eb913856187..79e1cf4f1b63 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 @@ package com.android.systemui.biometrics; 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; @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 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; @@ -89,6 +90,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { private ConfigurationController mConfigurationController; @Mock private UdfpsController mUdfpsController; + private FakeSystemClock mSystemClock = new FakeSystemClock(); private UdfpsKeyguardViewController mController; @@ -124,6 +126,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mKeyguardViewMediator, mLockscreenShadeTransitionController, mConfigurationController, + mSystemClock, mKeyguardStateController, mUdfpsController); } @@ -299,6 +302,59 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { } @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(); @@ -361,6 +417,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue(); } + + private void captureKeyguardStateControllerCallback() { verify(mKeyguardStateController).addCallback( mKeyguardStateControllerCallbackCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java index d6226aa53f67..a32cb9b6baa9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -43,6 +43,7 @@ public class DozeConfigurationUtil { when(params.singleTapUsesProx()).thenReturn(true); when(params.longPressUsesProx()).thenReturn(true); when(params.getQuickPickupAodDuration()).thenReturn(500); + when(params.brightnessUsesProx()).thenReturn(true); doneHolder[0] = true; return params; 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 4e8b59c95681..deb7d31d87a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -29,6 +29,7 @@ import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED; 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; @@ -47,6 +48,7 @@ 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.util.concurrency.FakeExecutor; @@ -82,6 +84,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { WakefulnessLifecycle mWakefulnessLifecycle; @Mock DozeParameters mDozeParameters; + @Mock + DockManager mDockManager; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor); @@ -109,7 +113,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { mSensor = fakeSensorManager.getFakeLightSensor(); mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters); + mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); mScreen.onScreenState(Display.STATE_ON); } @@ -157,6 +161,67 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test + public void testAodDocked_doNotSelectivelyUseProx_usesLightSensor() { + // GIVEN the device doesn't need to selectively register for prox sensors and + // brightness sensor uses prox + when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(false); + when(mDozeParameters.brightnessUsesProx()).thenReturn(true); + + // GIVEN the device is docked and the display state changes to ON + when(mDockManager.isDocked()).thenReturn(true); + mScreen.onScreenState(Display.STATE_ON); + waitForSensorManager(); + + // WHEN new sensor event sent + mSensor.sendSensorEvent(3); + + // THEN brightness is updated + assertEquals(3, mServiceFake.screenBrightness); + } + + @Test + public void testAodDocked_brightnessDoesNotUseProx_usesLightSensor() { + // GIVEN the device doesn't need to selectively register for prox sensors but + // the brightness sensor doesn't use prox + when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(true); + when(mDozeParameters.brightnessUsesProx()).thenReturn(false); + + // GIVEN the device is docked and the display state changes to ON + when(mDockManager.isDocked()).thenReturn(true); + mScreen.onScreenState(Display.STATE_ON); + waitForSensorManager(); + + // WHEN new sensor event sent + mSensor.sendSensorEvent(3); + + // THEN brightness is updated + assertEquals(3, mServiceFake.screenBrightness); + } + + + @Test + public void testAodDocked_noProx_brightnessUsesProx_doNotUseLightSensor() { + final int startBrightness = mServiceFake.screenBrightness; + + // GIVEN the device needs to selectively register for prox sensors and + // the brightness sensor uses prox + when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(true); + when(mDozeParameters.brightnessUsesProx()).thenReturn(true); + + // GIVEN the device is docked and the display state is on + when(mDockManager.isDocked()).thenReturn(true); + mScreen.onScreenState(Display.STATE_ON); + waitForSensorManager(); + + // WHEN new sensor event sent + mSensor.sendSensorEvent(3); + + // THEN brightness is NOT changed + assertNotSame(3, mServiceFake.screenBrightness); + assertEquals(startBrightness, mServiceFake.screenBrightness); + } + + @Test public void testPausingAod_doesNotResetBrightness() throws Exception { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); @@ -175,7 +240,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { 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); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE); reset(mDozeHost); @@ -216,7 +281,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void testNullSensor() throws Exception { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.empty() /* sensor */, mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters); + mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); 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 0c94f09c8572..5c4c27ccc4ca 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 @@ public class DozeSensorsTest extends SysuiTestCase { 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 @@ public class DozeSensorsTest extends SysuiTestCase { 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 @@ public class DozeSensorsTest extends SysuiTestCase { } 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/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 578c2d976cce..509ef82ee426 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 com.google.common.truth.Truth.assertThat; 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.MetricsLogger; 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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private Handler mHandler; @Mock private UserContextProvider mUserContextProvider; @Mock private StatusBar mStatusBar; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private TestableLooper mTestableLooper; @@ -156,7 +159,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mSysUiState, mHandler, mPackageManager, - mStatusBar + mStatusBar, + mKeyguardUpdateMonitor ); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); @@ -422,4 +426,31 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { 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 2fa67cc0be60..338bb3037331 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.MetricsLogger; 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 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock private PackageManager mPackageManager; @Mock private SecureSettings mSecureSettings; @Mock private StatusBar mStatusBar; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private TestableLooper mTestableLooper; @@ -169,7 +171,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mSysUiState, mHandler, mPackageManager, - mStatusBar + mStatusBar, + mKeyguardUpdateMonitor ); mGlobalActionsDialog.setZeroDialogPressDelayForTesting(); 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 ad0878031679..31d70f5c811f 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.Mock; 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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, () -> mNotificationShadeDepthController); mViewMediator.start(); - mViewMediator.onSystemReady(); } @Test @@ -165,8 +164,10 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @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 new file mode 100644 index 000000000000..d279bbb02cee --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -0,0 +1,189 @@ +/* + * 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 junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PointF; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.os.Vibrator; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.LockIconView; +import com.android.keyguard.LockIconViewController; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; + +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 java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class LockIconViewControllerTest extends SysuiTestCase { + private @Mock LockIconView mLockIconView; + private @Mock Context mContext; + private @Mock Resources mResources; + private @Mock DisplayMetrics mDisplayMetrics; + private @Mock StatusBarStateController mStatusBarStateController; + private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private @Mock KeyguardViewController mKeyguardViewController; + private @Mock KeyguardStateController mKeyguardStateController; + private @Mock FalsingManager mFalsingManager; + private @Mock AuthController mAuthController; + private @Mock DumpManager mDumpManager; + private @Mock AccessibilityManager mAccessibilityManager; + private @Mock ConfigurationController mConfigurationController; + private @Mock DelayableExecutor mDelayableExecutor; + private @Mock Vibrator mVibrator; + private @Mock AuthRippleController mAuthRippleController; + + private LockIconViewController mLockIconViewController; + + // 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<AuthController.Callback> mAuthControllerCallbackCaptor; + private AuthController.Callback mAuthControllerCallback; + + @Captor private ArgumentCaptor<PointF> mPointCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mLockIconView.getResources()).thenReturn(mResources); + when(mLockIconView.getContext()).thenReturn(mContext); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + + mLockIconViewController = new LockIconViewController( + mLockIconView, + mStatusBarStateController, + mKeyguardUpdateMonitor, + mKeyguardViewController, + mKeyguardStateController, + mFalsingManager, + mAuthController, + mDumpManager, + mAccessibilityManager, + mConfigurationController, + mDelayableExecutor, + mVibrator, + mAuthRippleController + ); + } + + @Test + public void testUpdateFingerprintLocationOnInit() { + // GIVEN fp sensor location is available pre-init + 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)); + + // WHEN lock icon view controller is initialized and attached + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(null); + + // THEN lock icon view location is updated with the same coordinates as fpProps + verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius)); + assertEquals(udfpsLocation, mPointCaptor.getValue()); + } + + @Test + public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() { + // GIVEN fp sensor location is not available pre-init + when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); + when(mAuthController.getUdfpsProps()).thenReturn(null); + mLockIconViewController.init(); + + // GIVEN fp sensor location is available post-init + captureAuthControllerCallback(); + 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)); + + // 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()); + } + + private void captureAuthControllerCallback() { + verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture()); + mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue(); + } + + private void captureAttachListener() { + verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture()); + mAttachListener = mAttachCaptor.getValue(); + } +} 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 de7abf866f6a..922c6b648aab 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 android.testing.TestableLooper.RunWithLooper; 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 @@ public class AutoAddTrackerTest extends SysuiTestCase { 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); - mAutoTracker.initialize(); + mSecureSettings = new FakeSettings(); - assertTrue(mAutoTracker.isAdded(SAVER)); - assertTrue(mAutoTracker.isAdded(WORK)); - assertFalse(mAutoTracker.isAdded(INVERSION)); + mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER); - // 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(); + mAutoTracker = createAutoAddTracker(USER); + mAutoTracker.initialize(); } @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 @@ public class AutoAddTrackerTest extends SysuiTestCase { @Test public void testSetAdded() { - mAutoTracker = new AutoAddTracker(mContext, USER); - mAutoTracker.initialize(); - assertFalse(mAutoTracker.isAdded(SAVER)); mAutoTracker.setTileAdded(SAVER); @@ -100,14 +110,12 @@ public class AutoAddTrackerTest extends SysuiTestCase { @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 @@ public class AutoAddTrackerTest extends SysuiTestCase { @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/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 9e97f801be3e..84bc12f6e922 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 @@ package com.android.systemui.qs; 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; @@ -67,12 +64,12 @@ import com.android.systemui.statusbar.phone.AutoTileManager; 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; @@ -120,7 +117,6 @@ public class QSTileHostTest extends SysuiTestCase { private UiEventLogger mUiEventLogger; @Mock private UserTracker mUserTracker; - @Mock private SecureSettings mSecureSettings; @Mock private CustomTileStatePersister mCustomTileStatePersister; @@ -134,14 +130,15 @@ public class QSTileHostTest extends SysuiTestCase { 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); setUpTileFactory(); - - when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt())) - .thenReturn(""); } private void setUpTileFactory() { @@ -364,6 +361,16 @@ public class QSTileHostTest extends SysuiTestCase { .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, @@ -389,14 +396,11 @@ public class QSTileHostTest extends SysuiTestCase { @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 35360bd19393..8b7e20ed0e5a 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.FeatureFlags 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 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @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 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { 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 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { colorExtractor, privacyDialogController, qsExpansionPathInterpolator, - featureFlags + featureFlags, + variableDateViewControllerFactory ) } @@ -274,6 +285,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `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 63ebe9290f64..23e51687f9e1 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.assertEquals; 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 @@ public class QSIconViewImplTest extends SysuiTestCase { } @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 class QSIconViewImplTest extends SysuiTestCase { 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/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index d44a52607707..e939411e4a2a 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 @@ package com.android.systemui.qs.tiles; 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 @@ public class CastTileTest extends SysuiTestCase { 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/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java new file mode 100644 index 000000000000..fa5f70c5a2fc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java @@ -0,0 +1,127 @@ +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.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.LinearLayout; + +import androidx.test.filters.SmallTest; + +import com.android.settingslib.wifi.WifiUtils; +import com.android.systemui.SysuiTestCase; +import com.android.wifitrackerlib.WifiEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +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"; + + @Mock + private WifiEntry mInternetWifiEntry; + @Mock + private List<WifiEntry> mWifiEntries; + @Mock + private WifiEntry mWifiEntry; + @Mock + private InternetDialogController mInternetDialogController; + @Mock + private WifiUtils.InternetIconInjector mWifiIconInjector; + + private InternetAdapter mInternetAdapter; + private InternetAdapter.InternetViewHolder mViewHolder; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + 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.mWifiLockedIcon.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.mWifiLockedIcon.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()); + } +} 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 000000000000..cacc4095d141 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -0,0 +1,491 @@ +package com.android.systemui.qs.tiles.dialog; + +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.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.content.Context; +import android.content.Intent; +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.text.TextUtils; + +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.util.concurrency.FakeExecutor; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.time.FakeSystemClock; +import com.android.wifitrackerlib.WifiEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +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"; + + @Mock + private WifiManager mWifiManager; + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private SubscriptionManager mSubscriptionManager; + @Mock + private Handler mHandler; + @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 ServiceState mServiceState; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private WifiUtils.InternetIconInjector mWifiIconInjector; + @Mock + InternetDialogController.InternetDialogCallback mInternetDialogCallback; + + 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); + 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(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); + + 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); + mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, + mInternetDialogController.mOnSubscriptionsChangedListener); + mInternetDialogController.onStart(mInternetDialogCallback, true); + mInternetDialogController.onAccessPointsChanged(mAccessPoints); + mInternetDialogController.mActivityStarter = mActivityStarter; + mInternetDialogController.mWifiIconInjector = mWifiIconInjector; + } + + @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_withNoConnectedEntry_returnNull() { + mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); + + assertThat(mInternetDialogController.getWifiDetailsSettingsIntent()).isNull(); + } + + @Test + public void getWifiDetailsSettingsIntent_withNoConnectedEntryKey_returnNull() { + when(mConnectedEntry.getKey()).thenReturn(null); + + assertThat(mInternetDialogController.getWifiDetailsSettingsIntent()).isNull(); + } + + @Test + public void getWifiDetailsSettingsIntent_withConnectedEntryKey_returnIntent() { + when(mConnectedEntry.getKey()).thenReturn("test_key"); + + assertThat(mInternetDialogController.getWifiDetailsSettingsIntent()).isNotNull(); + } + + @Test + public void getWifiDrawable_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 launchWifiNetworkDetailsSetting_withNoConnectedEntry_doNothing() { + mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); + + mInternetDialogController.launchWifiNetworkDetailsSetting(); + + verify(mActivityStarter, never()) + .postStartActivityDismissingKeyguard(any(Intent.class), anyInt()); + } + + @Test + public void launchWifiNetworkDetailsSetting_withConnectedEntryKey_startActivity() { + when(mConnectedEntry.getKey()).thenReturn("test_key"); + + mInternetDialogController.launchWifiNetworkDetailsSetting(); + + 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); + } + + 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) { + super(context, uiEventLogger, starter, accessPointController, subscriptionManager, + telephonyManager, wifiManager, connectivityManager, handler, mainExecutor, + broadcastDispatcher, keyguardUpdateMonitor, globalSettings, + keyguardStateController); + 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 000000000000..fa9c0538e9bd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -0,0 +1,292 @@ +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 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); + 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_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 10c878a92745..6f081c759df7 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 android.view.ScrollCaptureResponse; 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 @@ public class ScrollCaptureControllerTest extends SysuiTestCase { 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/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index f5ce673c249e..4c9006316175 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -111,6 +111,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { 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 +183,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { 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); @@ -677,6 +679,34 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { 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)); + } + 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 000000000000..97fe25d9a619 --- /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 a7b14460f925..7f72f194d9d9 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.View 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 @@ -178,10 +179,21 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @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/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index ddd78541d113..90b8a74d88be 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 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { 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 6c1a3c90d83d..bcc257dfa3d9 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; @@ -55,6 +60,8 @@ import com.android.systemui.util.InjectionInflationController; 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; @@ -91,6 +98,10 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; + @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler> + mInteractionEventHandlerCaptor; + private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -147,4 +158,49 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { 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 6de58667fb01..47c8806be6d4 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 @@ public class ScrimControllerTest extends SysuiTestCase { assertScrimTinted(Map.of( mScrimInFront, false, - mScrimBehind, false, + mScrimBehind, true, mScrimForBubble, true )); @@ -705,7 +705,7 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { @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/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index 57198dbe18bb..4a5770d12239 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 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { 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 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { 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 000000000000..871a48c503be --- /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 9c47f19b20c8..e6dc4db34f91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -96,6 +96,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { DumpManager mDumpManager; @Mock OverlayManagerTransaction.Builder mTransactionBuilder; + @Mock + Runnable mOnOverlaysApplied; private ThemeOverlayApplier mManager; private boolean mGetOverlayInfoEnabled = true; @@ -103,7 +105,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @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() { @@ -173,7 +176,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_allEnabledExclusively() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, mOnOverlaysApplied); verify(mOverlayManager).commit(any()); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { @@ -185,7 +188,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, mOnOverlaysApplied); for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) { if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) { @@ -202,8 +205,9 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { public void allCategoriesSpecified_enabledForAllUserHandles() { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), - userHandles); + userHandles, mOnOverlaysApplied); + verify(mOnOverlaysApplied).run(); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), eq(TEST_USER.getIdentifier())); @@ -219,7 +223,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), - userHandles); + userHandles, mOnOverlaysApplied); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true), @@ -233,7 +237,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { mock(FabricatedOverlay.class) }; mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, - TEST_USER.getIdentifier(), TEST_USER_HANDLES); + TEST_USER.getIdentifier(), TEST_USER_HANDLES, mOnOverlaysApplied); for (FabricatedOverlay overlay : pendingCreation) { verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay)); @@ -247,7 +251,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID); mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, mOnOverlaysApplied); for (OverlayIdentifier overlayPackage : categoryToPackage.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), @@ -264,7 +268,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void zeroCategoriesSpecified_allDisabled() { mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, mOnOverlaysApplied); for (String category : THEME_CATEGORIES) { verify(mTransactionBuilder).setEnabled( @@ -279,7 +283,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category")); mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, mOnOverlaysApplied); verify(mTransactionBuilder, never()).setEnabled( eq(new OverlayIdentifier("com.example.blah.category")), eq(false), diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 07d3fc20983f..d3820f06c645 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -161,7 +161,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -183,7 +183,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), null, null), WallpaperManager.FLAG_SYSTEM); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -204,7 +204,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -240,7 +240,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { .isFalse(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -270,7 +270,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { "android.theme.customization.color_both\":\"0")).isTrue(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -300,7 +300,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { "android.theme.customization.color_both\":\"1")).isTrue(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -327,7 +327,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index")) .isFalse(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -354,7 +354,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index")) .isFalse(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -382,7 +382,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture()); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -411,14 +411,14 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test public void onProfileAdded_setsTheme() { mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -428,7 +428,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -438,7 +438,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -450,7 +450,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { Color.valueOf(Color.BLUE), null); mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); // Regression test: null events should not reset the internal state and allow colors to be // applied again. @@ -458,11 +458,11 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED)); mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM); verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), - any()); + any(), any()); mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.GREEN), null, null), WallpaperManager.FLAG_SYSTEM); verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), - any()); + any(), any()); } @Test @@ -499,7 +499,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture()); // Colors were applied during controller initialization. - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); clearInvocations(mThemeOverlayApplier); } @@ -533,7 +533,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture()); // Colors were applied during controller initialization. - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); clearInvocations(mThemeOverlayApplier); WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), @@ -542,12 +542,12 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // Defers event because we already have initial colors. verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); // Then event happens after setup phase is over. when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); mDeviceProvisionedListener.getValue().onUserSetupChanged(); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -568,11 +568,11 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { Color.valueOf(Color.RED), null); mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); mWakefulnessLifecycle.dispatchFinishedGoingToSleep(); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -592,10 +592,10 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { Color.valueOf(Color.RED), null); mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep(); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -614,7 +614,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) 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 000000000000..eebcbe63d004 --- /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 a34c5986f36c..0e9d96c61e54 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 @@ public class ProximitySensorDualTest extends SysuiTestCase { @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 @@ public class ProximitySensorDualTest extends SysuiTestCase { 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 69764227040b..7bb26748a9d9 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 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti 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 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti @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 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti 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 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti 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 0d560f237a07..34cae58d30e1 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.never; 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 @@ public class FakeSettingsTest extends SysuiTestCase { } @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 @@ public class FakeSettingsTest extends SysuiTestCase { 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/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 5c9d38515e49..2e3e635c1157 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 @@ final class CoreSettingsObserver extends ContentObserver { 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/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index af8d7a6a282b..3003c520254b 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 @@ public final class GameManagerService extends IGameManagerService.Stub { "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 @@ public final class GameManagerService extends IGameManagerService.Stub { private boolean mPerfModeOptedIn; private boolean mBatteryModeOptedIn; private boolean mAllowDownscale; + private boolean mAllowAngle; GamePackageConfiguration(String packageName, int userId) { mPackageName = packageName; @@ -305,10 +313,12 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { * @hide */ public String toString() { - return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]"; + return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:" + + mUseAngle + "]"; } /** @@ -384,13 +411,14 @@ public final class GameManagerService extends IGameManagerService.Stub { } /** - * 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { if (DEBUG) { Slog.v(TAG, dumpDeviceConfigs()); } - if (packageConfig.isGameModeOptedIn(gameMode)) { + if (packageConfig.willGamePerformOptimizations(gameMode)) { disableCompatScale(packageName); return; } @@ -782,6 +837,17 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index b58076631feb..5ecdfe49cdf7 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1719,10 +1719,10 @@ import java.util.concurrent.atomic.AtomicBoolean; 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 34e2578f7855..3c0fe717a722 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5295,6 +5295,10 @@ public class AudioService extends IAudioService.Stub // 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 +5324,10 @@ public class AudioService extends IAudioService.Stub /** @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 +5350,10 @@ public class AudioService extends IAudioService.Stub /** @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()") diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 4c9d0f2691b3..2ae5cbbbf24b 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 @@ public class DisplayDeviceConfig { 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 afd18894f531..c2200430e53b 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 @@ public final class DisplayManagerService extends SystemService { 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 @@ public final class DisplayManagerService extends SystemService { // 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b54e8f973bd6..78c909d92c58 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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()) { @@ -7451,15 +7449,21 @@ public class NotificationManagerService extends SystemService { 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 +7471,13 @@ public class NotificationManagerService extends SystemService { 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 +7581,19 @@ public class NotificationManagerService extends SystemService { } @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 +7632,8 @@ public class NotificationManagerService extends SystemService { 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; } @@ -8758,10 +8777,22 @@ public class NotificationManagerService extends SystemService { 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/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 96bde3df1e68..e8a3a8150377 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 @@ public class PreferencesHelper implements RankingConfig { } } - 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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f6acad0194c1..1eb047bf1677 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub } 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 @@ public class PackageManagerService extends IPackageManager.Stub } } // 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub t.traceEnd(); mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions); + mPermissionManager.readLegacyPermissionStateTEMP(); if (!mOnlyCore && mFirstBoot) { requestCopyPreoptedFiles(mInjector); @@ -7637,7 +7972,6 @@ public class PackageManagerService extends IPackageManager.Stub + ((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 @@ public class PackageManagerService extends IPackageManager.Stub /** * 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 @@ public class PackageManagerService extends IPackageManager.Stub } @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; } /* @@ -23470,7 +23589,7 @@ public class PackageManagerService extends IPackageManager.Stub 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; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index e40882268e67..b6ca67d25f77 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mLidNavigationAccessibility; int mShortPressOnPowerBehavior; int mLongPressOnPowerBehavior; + long mLongPressOnPowerAssistantTimeoutMs; int mVeryLongPressOnPowerBehavior; int mDoublePressOnPowerBehavior; int mTriplePressOnPowerBehavior; @@ -732,6 +733,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule { PowerKeyRule(int gestures) { - super(KEYCODE_POWER, gestures); + super(mContext, KEYCODE_POWER, gestures); } @Override @@ -1970,6 +1976,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @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 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule { BackKeyRule(int gestures) { - super(KEYCODE_BACK, gestures); + super(mContext, KEYCODE_BACK, gestures); } @Override @@ -2017,7 +2032,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void initSingleKeyGestureRules() { - mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext); + mSingleKeyGestureDetector = new SingleKeyGestureDetector(); int powerKeyGestures = 0; if (hasVeryLongPressOnPowerBehavior()) { @@ -2115,6 +2130,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 1ef2bf9151e0..6fee69bf0472 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 @@ public final class SingleKeyGestureDetector { 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 @@ public final class SingleKeyGestureDetector { * </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 @@ public final class SingleKeyGestureDetector { */ 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 final class SingleKeyGestureDetector { } } - 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 @@ public final class SingleKeyGestureDetector { 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 7555a7f2920b..c91d8dedc41b 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 @@ public class Notifier { 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 @@ public class Notifier { 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 class Notifier { 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 @@ public class Notifier { } /** + * 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 @@ public class Notifier { tm.notifyUserActivity(); mPolicy.userActivity(); mFaceDownDetector.userActivity(event); + mScreenUndimDetector.userActivity(); } void postEnhancedDischargePredictionBroadcast(long delayMs) { @@ -812,6 +832,10 @@ public class Notifier { 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 @@ public class Notifier { 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 73bce319d5f2..e7e0425d9171 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService mBatterySaverController.systemReady(); mBatterySaverPolicy.systemReady(); mFaceDownDetector.systemReady(mContext); + mScreenUndimDetector.systemReady(mContext); // Register for settings changes. resolver.registerContentObserver(Settings.Secure.getUriFor( @@ -3190,6 +3194,7 @@ public final class PowerManagerService extends SystemService 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 000000000000..951bc1f76e6d --- /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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 771332071756..80bc16a9b052 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; @@ -2045,7 +2046,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + 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 +2070,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub 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; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 3a4faf73bfe1..e02e8671f211 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.ActivityTaskManager.INVALID_TASK_ID; 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.ComponentName; 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.PersistableBundle; 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.policy.IKeyguardDismissCallback; 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 @@ class ActivityClientController extends IActivityClientController.Stub { @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 308df2f4e9a0..d13c8ba96d48 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -656,6 +656,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean mLastImeShown; /** + * When set to true, the IME insets will be frozen until the next app becomes IME input target. + * @see InsetsPolicy#adjustVisibilityForIme + */ + boolean mImeInsetsFrozenUntilStartInput; + + /** * A flag to determine if this AR is in the process of closing or entering PIP. This is needed * to help AR know that the app is in the process of closing but hasn't yet started closing on * the WM side. @@ -1363,6 +1369,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (newTask != null && isState(RESUMED)) { newTask.setResumedActivity(this, "onParentChanged"); + mImeInsetsFrozenUntilStartInput = false; } if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -4141,7 +4148,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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(); @@ -4759,6 +4766,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && imeInputTarget.getWindow().mActivityRecord == this && mDisplayContent.mInputMethodWindow != null && mDisplayContent.mInputMethodWindow.isVisible(); + mImeInsetsFrozenUntilStartInput = true; } final DisplayContent displayContent = getDisplayContent(); @@ -5877,6 +5885,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // closing activity having to wait until idle timeout to be stopped or destroyed if the // next activity won't report idle (e.g. repeated view animation). mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded(); + + // If the activity is visible, but no windows are eligible to start input, unfreeze + // to avoid permanently frozen IME insets. + if (mImeInsetsFrozenUntilStartInput && getWindow( + win -> WindowManager.LayoutParams.mayUseInputMethod(win.mAttrs.flags)) + == null) { + mImeInsetsFrozenUntilStartInput = false; + } } } @@ -7792,6 +7808,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + @Override + void onResize() { + // Reset freezing IME insets flag when the activity resized. + mImeInsetsFrozenUntilStartInput = false; + super.onResize(); + } + /** Returns true if the configuration is compatible with this activity. */ boolean isConfigurationCompatible(Configuration config) { final int orientation = getRequestedOrientation(); @@ -8482,6 +8505,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * 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 e3459a1edc0f..efa67e934543 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // 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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6892dbc158d4..9335846e7805 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3971,6 +3971,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void updateImeInputAndControlTarget(WindowState target) { if (mImeInputTarget != target) { ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target); + if (target != null && target.mActivityRecord != null) { + target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; + } setImeInputTarget(target); updateImeControlTarget(); } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 8c781a13f7db..d417d56b6d31 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_NAVIGATION_BAR_PANEL; 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 @@ final class InputMonitor { || 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/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index f2f192686ad5..a8e1c1cda72b 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -211,7 +211,7 @@ class InsetsPolicy { InsetsState getInsetsForWindow(WindowState target) { final InsetsState originalState = mStateController.getInsetsForWindow(target); final InsetsState state = adjustVisibilityForTransientTypes(originalState); - return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state; + return adjustVisibilityForIme(target, state, state == originalState); } /** @@ -241,16 +241,37 @@ class InsetsPolicy { return state; } - // Navigation bar insets is always visible to IME. - private static InsetsState adjustVisibilityForIme(InsetsState originalState, + private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState, boolean copyState) { - final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR); - if (originalNavSource != null && !originalNavSource.isVisible()) { - final InsetsState state = copyState ? new InsetsState(originalState) : originalState; - final InsetsSource navSource = new InsetsSource(originalNavSource); - navSource.setVisible(true); - state.addSource(navSource); - return state; + if (w.mIsImWindow) { + // Navigation bar insets is always visible to IME. + final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR); + if (originalNavSource != null && !originalNavSource.isVisible()) { + final InsetsState state = copyState ? new InsetsState(originalState) + : originalState; + final InsetsSource navSource = new InsetsSource(originalNavSource); + navSource.setVisible(true); + state.addSource(navSource); + return state; + } + } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) { + // During switching tasks with gestural navigation, if the IME is attached to + // one app window on that time, even the next app window is behind the IME window, + // conceptually the window should not receive the IME insets if the next window is + // not eligible IME requester and ready to show IME on top of it. + final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp(); + final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME); + + if (shouldImeAttachedToApp && originalImeSource != null) { + final boolean imeVisibility = + w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME); + final InsetsState state = copyState ? new InsetsState(originalState) + : originalState; + final InsetsSource imeSource = new InsetsSource(originalImeSource); + imeSource.setVisible(imeVisibility); + state.addSource(imeSource); + return state; + } } return originalState; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9caef70f6b51..2913e1071ed6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7070,6 +7070,7 @@ public class WindowManagerService extends IWindowManager.Stub "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 +8598,9 @@ public class WindowManagerService extends IWindowManager.Stub 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/tests/PackageManager/packageinstaller/Android.bp b/services/tests/PackageManager/packageinstaller/Android.bp new file mode 100644 index 000000000000..35d754b4adc5 --- /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 000000000000..d7062587c4bc --- /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 000000000000..c39285ffca38 --- /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 000000000000..d7d2726c1583 --- /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 17a5dccb57da..3cab5ecd3de1 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/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 4d86c8716bf1..8840057cc552 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 @@ public class GameManagerServiceTests { } 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); } @@ -212,7 +219,8 @@ public class GameManagerServiceTests { } 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 @@ public class GameManagerServiceTests { } 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 @@ public class GameManagerServiceTests { } 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 @@ public class GameManagerServiceTests { } 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 @@ public class GameManagerServiceTests { } 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 @@ public class GameManagerServiceTests { .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. */ @@ -427,6 +460,19 @@ public class GameManagerServiceTests { 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 +638,50 @@ public class GameManagerServiceTests { } /** + * 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/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java new file mode 100644 index 000000000000..f94377fe51c2 --- /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/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java index 5012ca9ab420..6e3f754e4882 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 @@ public class NotifierTest { @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 @@ public class NotifierTest { 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 5eabc1bea148..3d64d4b5abb9 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 @@ public class PowerManagerServiceTest { @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/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 71c05b5c46f7..ea46eab6e8f9 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 junit.framework.Assert.assertTrue; 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.provider.Settings; 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { } @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/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index f57c416e4a97..f660af02a0f6 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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); @@ -4793,6 +4829,52 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index bf0ed713e4c3..66d157708332 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.RemoteCallback; 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { * 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { @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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 222c692a9778..6b36fe808a2e 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 @@ public class SingleKeyGestureTests { @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 @@ public class SingleKeyGestureTests { } 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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 293e862a6b74..6f04f176afd8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -39,6 +39,7 @@ import static android.os.Process.NOBODY_UID; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -2816,6 +2817,73 @@ public class ActivityRecordTests extends WindowTestsBase { assertFalse(activity.mDisplayContent.mClosingApps.contains(activity)); } + @Test + public void testImeInsetsFrozenFlag_resetWhenReparented() { + final ActivityRecord activity = createActivityWithTask(); + final WindowState app = createWindow(null, TYPE_APPLICATION, activity, "app"); + final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow"); + final Task newTask = new TaskBuilder(mSupervisor).build(); + makeWindowVisible(app, imeWindow); + mDisplayContent.mInputMethodWindow = imeWindow; + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + + // Simulate app is closing and expect the last IME is shown and IME insets is frozen. + app.mActivityRecord.commitVisibility(false, false); + assertTrue(app.mActivityRecord.mLastImeShown); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Expect IME insets frozen state will reset when the activity is reparent to the new task. + activity.setState(RESUMED, "test"); + activity.reparent(newTask, 0 /* top */, "test"); + assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + } + + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testImeInsetsFrozenFlag_resetWhenResized() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + makeWindowVisibleAndDrawn(app, mImeWindow); + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + + // Simulate app is closing and expect the last IME is shown and IME insets is frozen. + app.mActivityRecord.commitVisibility(false, false); + assertTrue(app.mActivityRecord.mLastImeShown); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Expect IME insets frozen state will reset when the activity is reparent to the new task. + app.mActivityRecord.onResize(); + assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + } + + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + makeWindowVisibleAndDrawn(app, mImeWindow); + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + + // Simulate app is closing and expect the last IME is shown and IME insets is frozen. + app.mActivityRecord.commitVisibility(false, false); + app.mActivityRecord.onWindowsGone(); + + assertTrue(app.mActivityRecord.mLastImeShown); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Expect IME insets frozen state will reset when the activity has no IME focusable window. + app.mActivityRecord.forAllWindowsUnchecked(w -> { + w.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; + return true; + }, true); + + app.mActivityRecord.commitVisibility(true, false); + app.mActivityRecord.onWindowsVisible(); + + assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + } + private void assertHasStartingWindow(ActivityRecord atoken) { assertNotNull(atoken.mStartingSurface); assertNotNull(atoken.mStartingData); 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 0d177c14427b..19f9b758811f 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 com.google.common.truth.Truth.assertThat; 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 static org.mockito.Mockito.timeout; 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 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { 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/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index a8e17534e6ed..8e7ba4bc3293 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -258,6 +258,7 @@ public class SystemServicesTestRule implements TestRule { final ActivityManagerInternal amInternal = mAmService.mInternal; spyOn(amInternal); doNothing().when(amInternal).trimApplications(); + doNothing().when(amInternal).scheduleAppGcs(); doNothing().when(amInternal).updateCpuStats(); doNothing().when(amInternal).updateOomAdj(); doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 92b670ed9699..d88ac256be5c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -891,6 +891,40 @@ public class WindowStateTests extends WindowTestsBase { assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR)); } + @Test + public void testAdjustImeInsetsVisibilityWhenSwitchingApps() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); + final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow"); + spyOn(imeWindow); + doReturn(true).when(imeWindow).isVisible(); + mDisplayContent.mInputMethodWindow = imeWindow; + + final InsetsStateController controller = mDisplayContent.getInsetsStateController(); + controller.getImeSourceProvider().setWindow(imeWindow, null, null); + + // Simulate app requests IME with updating all windows Insets State when IME is above app. + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + assertTrue(mDisplayContent.shouldImeAttachedToApp()); + controller.getImeSourceProvider().scheduleShowImePostLayout(app); + controller.getImeSourceProvider().getSource().setVisible(true); + controller.updateAboveInsetsState(imeWindow, false); + + // Expect all app windows behind IME can receive IME insets visible. + assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible()); + assertTrue(app2.getInsetsState().getSource(ITYPE_IME).isVisible()); + + // Simulate app plays closing transition to app2. + app.mActivityRecord.commitVisibility(false, false); + assertTrue(app.mActivityRecord.mLastImeShown); + assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Verify the IME insets is visible on app, but not for app2 during app task switching. + assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible()); + assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible()); + } + @UseTestDisplay(addWindows = { W_ACTIVITY }) @Test public void testUpdateImeControlTargetWhenLeavingMultiWindow() { 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 588089996d6c..611b3f5e62a5 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 @@ class WindowTestsBase extends SystemServiceTestsBase { 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 @@ class WindowTestsBase extends SystemServiceTestsBase { return this; } + ActivityBuilder setLaunchedFromPackage(String packageName) { + mLaunchedFromPackage = packageName; + return this; + } + ActivityBuilder setUseProcess(WindowProcessController wpc) { mWpc = wpc; return this; @@ -1000,6 +1006,7 @@ class WindowTestsBase extends SystemServiceTestsBase { final ActivityRecord activity = new ActivityRecord.Builder(mService) .setLaunchedFromPid(mLaunchedFromPid) .setLaunchedFromUid(mLaunchedFromUid) + .setLaunchedFromPackage(mLaunchedFromPackage) .setIntent(intent) .setActivityInfo(aInfo) .setActivityOptions(options) diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index 02bd0010de99..bc0a14667307 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -23,6 +23,8 @@ import android.os.Build; 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 final class Phone { 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 @@ public final class Phone { 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 @@ public final class Phone { 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 @@ public final class Phone { } } + 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 @@ public final class Phone { } final Call internalGetCallByTelecomId(String telecomId) { - return mCallByTelecomCallId.get(telecomId); + return getCallById(telecomId); } final void internalBringToForeground(boolean showDialpad) { @@ -249,35 +258,35 @@ public final class Phone { } 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(); } |