Merge "Add getPhoneNumber/setPhoneNumber API"
diff --git a/Android.bp b/Android.bp
index ecf6dee..36ed865 100644
--- a/Android.bp
+++ b/Android.bp
@@ -350,6 +350,7 @@
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
"modules-utils-preconditions",
+ "modules-utils-synchronous-result-receiver",
"modules-utils-os",
"framework-permission-aidl-java",
],
diff --git a/MULTIUSER_OWNERS b/MULTIUSER_OWNERS
index fbc611a..9d92e0f 100644
--- a/MULTIUSER_OWNERS
+++ b/MULTIUSER_OWNERS
@@ -1,4 +1,5 @@
# OWNERS of Multiuser related files
bookatz@google.com
+olilan@google.com
omakoto@google.com
yamasani@google.com
diff --git a/StubLibraries.bp b/StubLibraries.bp
index d090296..31fecd1 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -59,19 +59,13 @@
},
dists: [
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/public/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/public/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
@@ -115,19 +109,13 @@
},
dists: [
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/system/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/system/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
@@ -151,37 +139,25 @@
},
dists: [
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android.txt",
tag: ".api.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "removed.txt",
tag: ".removed-api.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
@@ -210,19 +186,13 @@
},
dists: [
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/module-lib/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/module-lib/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
@@ -282,10 +252,7 @@
java_version: "1.8",
compile_dex: true,
dist: {
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
tag: ".jar",
dest: "android-non-updatable.jar",
},
@@ -341,10 +308,7 @@
java_defaults {
name: "android_stubs_dists_default",
dist: {
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
tag: ".jar",
dest: "android.jar",
},
@@ -376,10 +340,7 @@
dists: [
{
// Legacy dist path
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
tag: ".jar",
dest: "android_system.jar",
},
@@ -412,6 +373,7 @@
static_libs: [
"android-non-updatable.stubs.module_lib",
"art.module.public.api.stubs.module_lib",
+ "i18n.module.public.api.stubs",
],
dist: {
dir: "apistubs/android/module-lib",
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index c251529a..e5b0742 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -125,7 +125,7 @@
* will not be invoked.
*
* @param params Parameters specifying info about this job, including the optional
- * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle).
+ * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle)}.
* This object serves to identify this specific running job instance when calling
* {@link #jobFinished(JobParameters, boolean)}.
* @return {@code true} if your service will continue running, using a separate thread
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 45588e8..0f2ce01 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2918,6 +2918,13 @@
reasonCode, reason).sendToTarget();
}
reportTempWhitelistChangedLocked(uid, true);
+ } else {
+ // The uid is already temp allowlisted, only need to update AMS for temp allowlist
+ // duration.
+ if (mLocalActivityManager != null) {
+ mLocalActivityManager.updateDeviceIdleTempAllowlist(null, uid, true,
+ duration, tempAllowListType, reasonCode, reason, callingUid);
+ }
}
}
if (informWhitelistChanged) {
@@ -3295,7 +3302,7 @@
if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
resetLightIdleManagementLocked();
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
- mConstants.FLEX_TIME_SHORT);
+ mConstants.FLEX_TIME_SHORT, true);
EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
}
}
@@ -3370,7 +3377,7 @@
mLightState = LIGHT_STATE_PRE_IDLE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT,
- mConstants.FLEX_TIME_SHORT);
+ mConstants.FLEX_TIME_SHORT, true);
break;
}
// Nothing active, fall through to immediately idle.
@@ -3389,7 +3396,7 @@
}
}
mMaintenanceStartTime = 0;
- scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex);
+ scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex, false);
mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
(long) (mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
mNextLightIdleDelayFlex = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT_FLEX,
@@ -3413,7 +3420,7 @@
} else if (mCurLightIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
}
- scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT);
+ scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT, true);
if (DEBUG) Slog.d(TAG,
"Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
@@ -3424,7 +3431,8 @@
// We'd like to do maintenance, but currently don't have network
// connectivity... let's try to wait until the network comes back.
// We'll only wait for another full idle period, however, and then give up.
- scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex / 2);
+ scheduleLightAlarmLocked(mNextLightIdleDelay,
+ mNextLightIdleDelayFlex / 2, true);
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
@@ -3941,6 +3949,10 @@
if (idleUntil) {
mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
+ } else if (mState == STATE_LOCATING) {
+ // Use setExact so we don't keep the GPS active for too long.
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
} else {
if (mConstants.USE_WINDOW_ALARMS) {
mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
@@ -3953,18 +3965,23 @@
}
}
- void scheduleLightAlarmLocked(long delay, long flex) {
+ @GuardedBy("this")
+ void scheduleLightAlarmLocked(long delay, long flex, boolean wakeup) {
if (DEBUG) {
- Slog.d(TAG, "scheduleLightAlarmLocked(" + delay
+ Slog.d(TAG, "scheduleLightAlarmLocked(wakeup=" + wakeup + ", " + delay
+ (mConstants.USE_WINDOW_ALARMS ? "/" + flex : "") + ")");
}
mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
if (mConstants.USE_WINDOW_ALARMS) {
- mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextLightAlarmTime, flex,
- "DeviceIdleController.light", mLightAlarmListener, mHandler);
+ mAlarmManager.setWindow(wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP :
+ AlarmManager.ELAPSED_REALTIME,
+ mNextLightAlarmTime, flex, "DeviceIdleController.light", mLightAlarmListener,
+ mHandler);
} else {
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextLightAlarmTime,
- "DeviceIdleController.light", mLightAlarmListener, mHandler);
+ mAlarmManager.set(wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP :
+ AlarmManager.ELAPSED_REALTIME,
+ mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener,
+ mHandler);
}
}
diff --git a/api/Android.bp b/api/Android.bp
index ed2247b..c6ea175 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -120,10 +120,7 @@
dest: "current.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/public/api",
dest: "android.txt",
},
@@ -205,10 +202,7 @@
dest: "removed.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/public/api",
dest: "removed.txt",
},
@@ -244,10 +238,7 @@
dest: "system-current.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/system/api",
dest: "android.txt",
},
@@ -302,10 +293,7 @@
dest: "system-removed.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/system/api",
dest: "removed.txt",
},
@@ -342,10 +330,7 @@
dest: "module-lib-current.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/module-lib/api",
dest: "android.txt",
},
@@ -402,10 +387,7 @@
dest: "module-lib-removed.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/module-lib/api",
dest: "removed.txt",
},
@@ -446,10 +428,7 @@
dest: "system-server-current.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/system-server/api",
dest: "android.txt",
},
@@ -473,10 +452,7 @@
dest: "system-server-removed.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/system-server/api",
dest: "removed.txt",
},
@@ -514,9 +490,6 @@
tools: ["api_versions_trimmer"],
cmd: "$(location api_versions_trimmer) $(out) $(in)",
dist: {
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
},
}
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp
index b2b66c2..3534624 100644
--- a/cmds/bootanimation/Android.bp
+++ b/cmds/bootanimation/Android.bp
@@ -71,7 +71,7 @@
"libui",
"libjnigraphics",
"libEGL",
- "libGLESv1_CM",
+ "libGLESv2",
"libgui",
],
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 6b8a775..30a8a8e 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -52,9 +52,8 @@
#include <gui/DisplayEventReceiver.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
#include <EGL/eglext.h>
#include "BootAnimation.h"
@@ -108,6 +107,93 @@
static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
static constexpr size_t TEXT_POS_LEN_MAX = 16;
+static const int DYNAMIC_COLOR_COUNT = 4;
+static const char U_TEXTURE[] = "uTexture";
+static const char U_FADE[] = "uFade";
+static const char U_CROP_AREA[] = "uCropArea";
+static const char U_START_COLOR_PREFIX[] = "uStartColor";
+static const char U_END_COLOR_PREFIX[] = "uEndColor";
+static const char U_COLOR_PROGRESS[] = "uColorProgress";
+static const char A_UV[] = "aUv";
+static const char A_POSITION[] = "aPosition";
+static const char VERTEX_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ attribute vec4 aPosition;
+ attribute highp vec2 aUv;
+ varying highp vec2 vUv;
+ void main() {
+ gl_Position = aPosition;
+ vUv = aUv;
+ })";
+static const char IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ const float cWhiteMaskThreshold = 0.05;
+ uniform sampler2D uTexture;
+ uniform float uFade;
+ uniform float uColorProgress;
+ uniform vec4 uStartColor0;
+ uniform vec4 uStartColor1;
+ uniform vec4 uStartColor2;
+ uniform vec4 uStartColor3;
+ uniform vec4 uEndColor0;
+ uniform vec4 uEndColor1;
+ uniform vec4 uEndColor2;
+ uniform vec4 uEndColor3;
+ varying highp vec2 vUv;
+ void main() {
+ vec4 mask = texture2D(uTexture, vUv);
+ float r = mask.r;
+ float g = mask.g;
+ float b = mask.b;
+ float a = mask.a;
+ // If all channels have values, render pixel as a shade of white.
+ float useWhiteMask = step(cWhiteMaskThreshold, r)
+ * step(cWhiteMaskThreshold, g)
+ * step(cWhiteMaskThreshold, b)
+ * step(cWhiteMaskThreshold, a);
+ vec4 color = r * mix(uStartColor0, uEndColor0, uColorProgress)
+ + g * mix(uStartColor1, uEndColor1, uColorProgress)
+ + b * mix(uStartColor2, uEndColor2, uColorProgress)
+ + a * mix(uStartColor3, uEndColor3, uColorProgress);
+ color = mix(color, vec4(vec3((r + g + b + a) * 0.25), 1.0), useWhiteMask);
+ gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+ })";
+static const char IMAGE_FRAG_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ uniform sampler2D uTexture;
+ uniform float uFade;
+ varying highp vec2 vUv;
+ void main() {
+ vec4 color = texture2D(uTexture, vUv);
+ gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+ })";
+static const char TEXT_FRAG_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ uniform sampler2D uTexture;
+ uniform vec4 uCropArea;
+ varying highp vec2 vUv;
+ void main() {
+ vec2 uv = vec2(mix(uCropArea.x, uCropArea.z, vUv.x),
+ mix(uCropArea.y, uCropArea.w, vUv.y));
+ gl_FragColor = texture2D(uTexture, uv);
+ })";
+
+static GLfloat quadPositions[] = {
+ -0.5f, -0.5f,
+ +0.5f, -0.5f,
+ +0.5f, +0.5f,
+ +0.5f, +0.5f,
+ -0.5f, +0.5f,
+ -0.5f, -0.5f
+};
+static GLfloat quadUVs[] = {
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ 1.0f, 0.0f,
+ 0.0f, 0.0f,
+ 0.0f, 1.0f
+};
// ---------------------------------------------------------------------------
@@ -163,7 +249,8 @@
requestExit();
}
-static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo) {
+static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo,
+ bool premultiplyAlpha) {
AImageDecoder* decoder = nullptr;
AImageDecoder_createFromBuffer(encodedData, dataLength, &decoder);
if (!decoder) {
@@ -177,6 +264,10 @@
outInfo->stride = AImageDecoder_getMinimumStride(decoder);
outInfo->flags = 0;
+ if (!premultiplyAlpha) {
+ AImageDecoder_setUnpremultipliedRequired(decoder, true);
+ }
+
const size_t size = outInfo->stride * outInfo->height;
void* pixels = malloc(size);
int result = AImageDecoder_decodeImage(decoder, pixels, outInfo->stride, size);
@@ -190,13 +281,14 @@
}
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
- const char* name) {
+ const char* name, bool premultiplyAlpha) {
Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
if (asset == nullptr)
return NO_INIT;
AndroidBitmapInfo bitmapInfo;
- void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo);
+ void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo,
+ premultiplyAlpha);
auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };
asset->close();
@@ -209,7 +301,6 @@
const int w = bitmapInfo.width;
const int h = bitmapInfo.height;
- GLint crop[4] = { 0, h, w, -h };
texture->w = w;
texture->h = h;
@@ -237,18 +328,19 @@
break;
}
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return NO_ERROR;
}
-status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) {
+status_t BootAnimation::initTexture(FileMap* map, int* width, int* height,
+ bool premultiplyAlpha) {
AndroidBitmapInfo bitmapInfo;
- void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo);
+ void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo,
+ premultiplyAlpha);
auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };
// FileMap memory is never released until application exit.
@@ -263,7 +355,6 @@
const int w = bitmapInfo.width;
const int h = bitmapInfo.height;
- GLint crop[4] = { 0, h, w, -h };
int tw = 1 << (31 - __builtin_clz(w));
int th = 1 << (31 - __builtin_clz(h));
if (tw < w) tw <<= 1;
@@ -297,7 +388,10 @@
break;
}
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
*width = w;
*height = h;
@@ -470,7 +564,9 @@
eglInitialize(display, nullptr, nullptr);
EGLConfig config = getEglConfig(display);
EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
- EGLContext context = eglCreateContext(display, config, nullptr, nullptr);
+ // Initialize egl context with client version number 2.0.
+ EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+ EGLContext context = eglCreateContext(display, config, nullptr, contextAttributes);
EGLint w, h;
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
@@ -503,11 +599,6 @@
void BootAnimation::projectSceneToWindow() {
glViewport(0, 0, mWidth, mHeight);
glScissor(0, 0, mWidth, mHeight);
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrthof(0, static_cast<float>(mWidth), 0, static_cast<float>(mHeight), -1, 1);
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
}
void BootAnimation::resizeSurface(int newWidth, int newHeight) {
@@ -600,8 +691,76 @@
}
}
+GLuint compileShader(GLenum shaderType, const GLchar *source) {
+ GLuint shader = glCreateShader(shaderType);
+ glShaderSource(shader, 1, &source, 0);
+ glCompileShader(shader);
+ GLint isCompiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
+ if (isCompiled == GL_FALSE) {
+ SLOGE("Compile shader failed. Shader type: %d", shaderType);
+ GLint maxLength = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
+ std::vector<GLchar> errorLog(maxLength);
+ glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]);
+ SLOGE("Shader compilation error: %s", &errorLog[0]);
+ return 0;
+ }
+ return shader;
+}
+
+GLuint linkShader(GLuint vertexShader, GLuint fragmentShader) {
+ GLuint program = glCreateProgram();
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, fragmentShader);
+ glLinkProgram(program);
+ GLint isLinked = 0;
+ glGetProgramiv(program, GL_LINK_STATUS, (int *)&isLinked);
+ if (isLinked == GL_FALSE) {
+ SLOGE("Linking shader failed. Shader handles: vert %d, frag %d",
+ vertexShader, fragmentShader);
+ return 0;
+ }
+ return program;
+}
+
+void BootAnimation::initShaders() {
+ bool dynamicColoringEnabled = mAnimation != nullptr && mAnimation->dynamicColoringEnabled;
+ GLuint vertexShader = compileShader(GL_VERTEX_SHADER, (const GLchar *)VERTEX_SHADER_SOURCE);
+ GLuint imageFragmentShader =
+ compileShader(GL_FRAGMENT_SHADER, dynamicColoringEnabled
+ ? (const GLchar *)IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE
+ : (const GLchar *)IMAGE_FRAG_SHADER_SOURCE);
+ GLuint textFragmentShader =
+ compileShader(GL_FRAGMENT_SHADER, (const GLchar *)TEXT_FRAG_SHADER_SOURCE);
+
+ // Initialize image shader.
+ mImageShader = linkShader(vertexShader, imageFragmentShader);
+ GLint positionLocation = glGetAttribLocation(mImageShader, A_POSITION);
+ GLint uvLocation = glGetAttribLocation(mImageShader, A_UV);
+ mImageTextureLocation = glGetUniformLocation(mImageShader, U_TEXTURE);
+ mImageFadeLocation = glGetUniformLocation(mImageShader, U_FADE);
+ glEnableVertexAttribArray(positionLocation);
+ glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, quadPositions);
+ glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
+ glEnableVertexAttribArray(uvLocation);
+
+ // Initialize text shader.
+ mTextShader = linkShader(vertexShader, textFragmentShader);
+ positionLocation = glGetAttribLocation(mTextShader, A_POSITION);
+ uvLocation = glGetAttribLocation(mTextShader, A_UV);
+ mTextTextureLocation = glGetUniformLocation(mTextShader, U_TEXTURE);
+ mTextCropAreaLocation = glGetUniformLocation(mTextShader, U_CROP_AREA);
+ glEnableVertexAttribArray(positionLocation);
+ glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, quadPositions);
+ glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
+ glEnableVertexAttribArray(uvLocation);
+}
+
bool BootAnimation::threadLoop() {
bool result;
+ initShaders();
+
// We have no bootanimation file, so we use the stock android logo
// animation.
if (mZipFileName.isEmpty()) {
@@ -623,6 +782,8 @@
}
bool BootAnimation::android() {
+ glActiveTexture(GL_TEXTURE0);
+
SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
@@ -631,19 +792,16 @@
mCallbacks->init({});
// clear screen
- glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
+ glUseProgram(mImageShader);
+
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
- glEnable(GL_TEXTURE_2D);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-
// Blend state
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const nsecs_t startTime = systemTime();
do {
@@ -666,12 +824,12 @@
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
- glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
- glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
+ drawTexturedQuad(x, yc, mAndroid[1].w, mAndroid[1].h);
+ drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
- glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
+ drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
@@ -766,6 +924,20 @@
return true;
}
+// Parse a color represented as a signed decimal int string.
+// E.g. "-2757722" (whose hex 2's complement is 0xFFD5EBA6).
+// If the input color string is empty, set color with values in defaultColor.
+static void parseColorDecimalString(const std::string& colorString,
+ float color[3], float defaultColor[3]) {
+ if (colorString == "") {
+ memcpy(color, defaultColor, sizeof(float) * 3);
+ return;
+ }
+ int colorInt = atoi(colorString.c_str());
+ color[0] = ((float)((colorInt >> 16) & 0xFF)) / 0xFF; // r
+ color[1] = ((float)((colorInt >> 8) & 0xFF)) / 0xFF; // g
+ color[2] = ((float)(colorInt & 0xFF)) / 0xFF; // b
+}
static bool readFile(ZipFileRO* zip, const char* name, String8& outString) {
ZipEntryRO entry = zip->findEntryByName(name);
@@ -798,10 +970,10 @@
status = initTexture(font->map, &font->texture.w, &font->texture.h);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else if (fallback != nullptr) {
status = initTexture(&font->texture, mAssets, fallback);
} else {
@@ -816,40 +988,11 @@
return status;
}
-void BootAnimation::fadeFrame(const int frameLeft, const int frameBottom, const int frameWidth,
- const int frameHeight, const Animation::Part& part,
- const int fadedFramesCount) {
- glEnable(GL_BLEND);
- glEnableClientState(GL_VERTEX_ARRAY);
- glDisable(GL_TEXTURE_2D);
- // avoid creating a hole due to mixing result alpha with GL_REPLACE texture
- glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
-
- const float alpha = static_cast<float>(fadedFramesCount) / part.framesToFadeCount;
- glColor4f(part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], alpha);
-
- const float frameStartX = static_cast<float>(frameLeft);
- const float frameStartY = static_cast<float>(frameBottom);
- const float frameEndX = frameStartX + frameWidth;
- const float frameEndY = frameStartY + frameHeight;
- const GLfloat frameRect[] = {
- frameStartX, frameStartY,
- frameEndX, frameStartY,
- frameEndX, frameEndY,
- frameStartX, frameEndY
- };
- glVertexPointer(2, GL_FLOAT, 0, frameRect);
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glEnable(GL_TEXTURE_2D);
- glDisableClientState(GL_VERTEX_ARRAY);
- glDisable(GL_BLEND);
-}
-
void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
glEnable(GL_BLEND); // Allow us to draw on top of the animation
glBindTexture(GL_TEXTURE_2D, font.texture.name);
+ glUseProgram(mTextShader);
+ glUniform1i(mTextTextureLocation, 0);
const int len = strlen(str);
const int strWidth = font.char_width * len;
@@ -865,8 +1008,6 @@
*y = mHeight + *y - font.char_height;
}
- int cropRect[4] = { 0, 0, font.char_width, -font.char_height };
-
for (int i = 0; i < len; i++) {
char c = str[i];
@@ -878,13 +1019,13 @@
const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters
const int row = charPos / FONT_NUM_COLS;
const int col = charPos % FONT_NUM_COLS;
- cropRect[0] = col * font.char_width; // Left of column
- cropRect[1] = row * font.char_height * 2; // Top of row
- // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line
- cropRect[1] += bold ? 2 * font.char_height : font.char_height;
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
-
- glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height);
+ // Bold fonts are expected in the second half of each row.
+ float v0 = (row + (bold ? 0.5f : 0.0f)) / FONT_NUM_ROWS;
+ float u0 = ((float)col) / FONT_NUM_COLS;
+ float v1 = v0 + 1.0f / FONT_NUM_ROWS / 2;
+ float u1 = u0 + 1.0f / FONT_NUM_COLS;
+ glUniform4f(mTextCropAreaLocation, u0, v0, u1, v1);
+ drawTexturedQuad(*x, *y, font.char_width, font.char_height);
*x += font.char_width;
}
@@ -938,6 +1079,8 @@
return false;
}
char const* s = desString.string();
+ std::string dynamicColoringPartName = "";
+ bool postDynamicColoring = false;
// Parse the description file
for (;;) {
@@ -952,11 +1095,19 @@
int pause = 0;
int progress = 0;
int framesToFadeCount = 0;
+ int colorTransitionStart = 0;
+ int colorTransitionEnd = 0;
char path[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified
char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
+ char dynamicColoringPartNameBuffer[ANIM_ENTRY_NAME_MAX];
char pathType;
+ // start colors default to black if unspecified
+ char start_color_0[7] = "000000";
+ char start_color_1[7] = "000000";
+ char start_color_2[7] = "000000";
+ char start_color_3[7] = "000000";
int nextReadPos;
@@ -971,6 +1122,18 @@
} else {
animation.progressEnabled = false;
}
+ } else if (sscanf(l, "dynamic_colors %" STRTO(ANIM_PATH_MAX) "s #%6s #%6s #%6s #%6s %d %d",
+ dynamicColoringPartNameBuffer,
+ start_color_0, start_color_1, start_color_2, start_color_3,
+ &colorTransitionStart, &colorTransitionEnd)) {
+ animation.dynamicColoringEnabled = true;
+ parseColor(start_color_0, animation.startColors[0]);
+ parseColor(start_color_1, animation.startColors[1]);
+ parseColor(start_color_2, animation.startColors[2]);
+ parseColor(start_color_3, animation.startColors[3]);
+ animation.colorTransitionStart = colorTransitionStart;
+ animation.colorTransitionEnd = colorTransitionEnd;
+ dynamicColoringPartName = std::string(dynamicColoringPartNameBuffer);
} else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n",
&pathType, &count, &pause, path, &nextReadPos) >= 4) {
if (pathType == 'f') {
@@ -983,6 +1146,16 @@
// "clockPos1=%s, clockPos2=%s",
// pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2);
Animation::Part part;
+ if (path == dynamicColoringPartName) {
+ // Part is specified to use dynamic coloring.
+ part.useDynamicColoring = true;
+ part.postDynamicColoring = false;
+ postDynamicColoring = true;
+ } else {
+ // Part does not use dynamic coloring.
+ part.useDynamicColoring = false;
+ part.postDynamicColoring = postDynamicColoring;
+ }
part.playUntilComplete = pathType == 'c';
part.framesToFadeCount = framesToFadeCount;
part.count = count;
@@ -1166,19 +1339,16 @@
// Blend required to draw time on top of animation frames.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
- glBindTexture(GL_TEXTURE_2D, 0);
glEnable(GL_TEXTURE_2D);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
bool clockFontInitialized = false;
if (mClockEnabled) {
clockFontInitialized =
@@ -1193,6 +1363,10 @@
mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
}
+ if (mAnimation != nullptr && mAnimation->dynamicColoringEnabled) {
+ initDynamicColors();
+ }
+
playAnimation(*mAnimation);
if (mTimeCheckThread != nullptr) {
@@ -1218,6 +1392,55 @@
(lastDisplayedProgress == 0 || lastDisplayedProgress == 100);
}
+// Linear mapping from range <a1, a2> to range <b1, b2>
+float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+}
+
+void BootAnimation::drawTexturedQuad(float xStart, float yStart, float width, float height) {
+ // Map coordinates from screen space to world space.
+ float x0 = mapLinear(xStart, 0, mWidth, -1, 1);
+ float y0 = mapLinear(yStart, 0, mHeight, -1, 1);
+ float x1 = mapLinear(xStart + width, 0, mWidth, -1, 1);
+ float y1 = mapLinear(yStart + height, 0, mHeight, -1, 1);
+ // Update quad vertex positions.
+ quadPositions[0] = x0;
+ quadPositions[1] = y0;
+ quadPositions[2] = x1;
+ quadPositions[3] = y0;
+ quadPositions[4] = x1;
+ quadPositions[5] = y1;
+ quadPositions[6] = x1;
+ quadPositions[7] = y1;
+ quadPositions[8] = x0;
+ quadPositions[9] = y1;
+ quadPositions[10] = x0;
+ quadPositions[11] = y0;
+ glDrawArrays(GL_TRIANGLES, 0,
+ sizeof(quadPositions) / sizeof(quadPositions[0]) / 2);
+}
+
+void BootAnimation::initDynamicColors() {
+ for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+ parseColorDecimalString(
+ android::base::GetProperty("persist.bootanim.color" + std::to_string(i + 1), ""),
+ mAnimation->endColors[i], mAnimation->startColors[i]);
+ }
+ glUseProgram(mImageShader);
+ SLOGI("[BootAnimation] Dynamically coloring boot animation.");
+ for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+ float *startColor = mAnimation->startColors[i];
+ float *endColor = mAnimation->endColors[i];
+ glUniform4f(glGetUniformLocation(mImageShader,
+ (U_START_COLOR_PREFIX + std::to_string(i)).c_str()),
+ startColor[0], startColor[1], startColor[2], 1 /* alpha */);
+ glUniform4f(glGetUniformLocation(mImageShader,
+ (U_END_COLOR_PREFIX + std::to_string(i)).c_str()),
+ endColor[0], endColor[1], endColor[2], 1 /* alpha */);
+ }
+ mImageColorProgressLocation = glGetUniformLocation(mImageShader, U_COLOR_PROGRESS);
+}
+
bool BootAnimation::playAnimation(const Animation& animation) {
const size_t pcount = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
@@ -1230,7 +1453,6 @@
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
- glBindTexture(GL_TEXTURE_2D, 0);
// Handle animation package
if (part.animation != nullptr) {
@@ -1261,6 +1483,19 @@
for (size_t j=0 ; j<fcount ; j++) {
if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;
+ // Color progress is
+ // - the animation progress, normalized from
+ // [colorTransitionStart,colorTransitionEnd] to [0, 1] for the dynamic coloring
+ // part.
+ // - 0 for parts that come before,
+ // - 1 for parts that come after.
+ float colorProgress = part.useDynamicColoring
+ ? fmin(fmax(
+ ((float)j - animation.colorTransitionStart) /
+ fmax(animation.colorTransitionEnd -
+ animation.colorTransitionStart, 1.0f), 0.0f), 1.0f)
+ : (part.postDynamicColoring ? 1 : 0);
+
processDisplayEvents();
const int animationX = (mWidth - animation.width) / 2;
@@ -1272,44 +1507,38 @@
if (r > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
- if (part.count != 1) {
- glGenTextures(1, &frame.tid);
- glBindTexture(GL_TEXTURE_2D, frame.tid);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- }
+ glGenTextures(1, &frame.tid);
+ glBindTexture(GL_TEXTURE_2D, frame.tid);
int w, h;
- initTexture(frame.map, &w, &h);
+ // Set decoding option to alpha unpremultiplied so that the R, G, B channels
+ // of transparent pixels are preserved.
+ initTexture(frame.map, &w, &h, false /* don't premultiply alpha */);
}
const int xc = animationX + frame.trimX;
const int yc = animationY + frame.trimY;
- Region clearReg(Rect(mWidth, mHeight));
- clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
- if (!clearReg.isEmpty()) {
- Region::const_iterator head(clearReg.begin());
- Region::const_iterator tail(clearReg.end());
- glEnable(GL_SCISSOR_TEST);
- while (head != tail) {
- const Rect& r2(*head++);
- glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
- glClear(GL_COLOR_BUFFER_BIT);
- }
- glDisable(GL_SCISSOR_TEST);
- }
+ glClear(GL_COLOR_BUFFER_BIT);
// specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
// which is equivalent to mHeight - (yc + frame.trimHeight)
const int frameDrawY = mHeight - (yc + frame.trimHeight);
- glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight);
+ float fade = 0;
// if the part hasn't been stopped yet then continue fading if necessary
if (exitPending() && part.hasFadingPhase()) {
- fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part,
- ++fadedFramesCount);
+ fade = static_cast<float>(++fadedFramesCount) / part.framesToFadeCount;
if (fadedFramesCount >= part.framesToFadeCount) {
fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading
}
}
+ glUseProgram(mImageShader);
+ glUniform1i(mImageTextureLocation, 0);
+ glUniform1f(mImageFadeLocation, fade);
+ if (animation.dynamicColoringEnabled) {
+ glUniform1f(mImageColorProgressLocation, colorProgress);
+ }
+ glEnable(GL_BLEND);
+ drawTexturedQuad(xc, frameDrawY, frame.trimWidth, frame.trimHeight);
+ glDisable(GL_BLEND);
if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index f8a31c6..7a597da 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -31,7 +31,7 @@
#include <binder/IBinder.h>
#include <EGL/egl.h>
-#include <GLES/gl.h>
+#include <GLES2/gl2.h>
namespace android {
@@ -53,7 +53,7 @@
};
struct Font {
- FileMap* map;
+ FileMap* map = nullptr;
Texture texture;
int char_width;
int char_height;
@@ -62,7 +62,7 @@
struct Animation {
struct Frame {
String8 name;
- FileMap* map;
+ FileMap* map = nullptr;
int trimX;
int trimY;
int trimWidth;
@@ -90,6 +90,10 @@
uint8_t* audioData;
int audioLength;
Animation* animation;
+ // Controls if dynamic coloring is enabled for this part.
+ bool useDynamicColoring = false;
+ // Defines if this part is played after the dynamic coloring part.
+ bool postDynamicColoring = false;
bool hasFadingPhase() const {
return !playUntilComplete && framesToFadeCount > 0;
@@ -105,6 +109,12 @@
ZipFileRO* zip;
Font clockFont;
Font progressFont;
+ // Controls if dynamic coloring is enabled for the whole animation.
+ bool dynamicColoringEnabled = false;
+ int colorTransitionStart = 0; // Start frame of dynamic color transition.
+ int colorTransitionEnd = 0; // End frame of dynamic color transition.
+ float startColors[4][3]; // Start colors of dynamic color transition.
+ float endColors[4][3]; // End colors of dynamic color transition.
};
// All callbacks will be called from this class's internal thread.
@@ -163,9 +173,12 @@
int displayEventCallback(int fd, int events, void* data);
void processDisplayEvents();
- status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
- status_t initTexture(FileMap* map, int* width, int* height);
+ status_t initTexture(Texture* texture, AssetManager& asset, const char* name,
+ bool premultiplyAlpha = true);
+ status_t initTexture(FileMap* map, int* width, int* height,
+ bool premultiplyAlpha = true);
status_t initFont(Font* font, const char* fallback);
+ void initShaders();
bool android();
bool movie();
void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
@@ -173,6 +186,7 @@
void drawProgress(int percent, const Font& font, const int xPos, const int yPos);
void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight,
const Animation::Part& part, int fadedFramesCount);
+ void drawTexturedQuad(float xStart, float yStart, float width, float height);
bool validClock(const Animation::Part& part);
Animation* loadAnimation(const String8&);
bool playAnimation(const Animation&);
@@ -192,6 +206,7 @@
void checkExit();
void handleViewport(nsecs_t timestep);
+ void initDynamicColors();
sp<SurfaceComposerClient> mSession;
AssetManager mAssets;
@@ -218,6 +233,13 @@
sp<TimeCheckThread> mTimeCheckThread = nullptr;
sp<Callbacks> mCallbacks;
Animation* mAnimation = nullptr;
+ GLuint mImageShader;
+ GLuint mTextShader;
+ GLuint mImageFadeLocation;
+ GLuint mImageTextureLocation;
+ GLuint mTextCropAreaLocation;
+ GLuint mTextTextureLocation;
+ GLuint mImageColorProgressLocation;
};
// ---------------------------------------------------------------------------
diff --git a/cmds/idmap2/OWNERS b/cmds/idmap2/OWNERS
index 062ffd4..def9f40 100644
--- a/cmds/idmap2/OWNERS
+++ b/cmds/idmap2/OWNERS
@@ -1,4 +1,3 @@
set noparent
-toddke@google.com
patb@google.com
zyy@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index 0f294fa..230689d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9178,10 +9178,10 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disconnect();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean executeReliableWrite();
- method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @Deprecated public int getConnectionState(android.bluetooth.BluetoothDevice);
method public android.bluetooth.BluetoothDevice getDevice();
- method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
@@ -10091,6 +10091,7 @@
method @Nullable public String getPackageName();
method public int getUid();
method public boolean isTrusted(@NonNull android.content.Context);
+ method @NonNull public static android.content.AttributionSource myAttributionSource();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.AttributionSource> CREATOR;
}
@@ -21541,6 +21542,7 @@
public class MediaActionSound {
ctor public MediaActionSound();
method public void load(int);
+ method public static boolean mustPlayShutterSound();
method public void play(int);
method public void release();
field public static final int FOCUS_COMPLETE = 1; // 0x1
@@ -25723,11 +25725,14 @@
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+ field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+ field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
field public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
field public static final String COLUMN_REVIEW_RATING = "review_rating";
field public static final String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
+ field public static final String COLUMN_SCRAMBLED = "scrambled";
field public static final String COLUMN_SEARCHABLE = "searchable";
field public static final String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
field @Deprecated public static final String COLUMN_SEASON_NUMBER = "season_number";
@@ -25787,7 +25792,9 @@
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+ field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+ field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
field public static final String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes";
field public static final String COLUMN_RECORDING_DATA_URI = "recording_data_uri";
@@ -38281,9 +38288,11 @@
public abstract class CarrierService extends android.app.Service {
ctor public CarrierService();
- method public final void notifyCarrierNetworkChange(boolean);
+ method @Deprecated public final void notifyCarrierNetworkChange(boolean);
+ method public final void notifyCarrierNetworkChange(int, boolean);
method @CallSuper public android.os.IBinder onBind(android.content.Intent);
- method public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
+ method @Deprecated public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
+ method @Nullable public android.os.PersistableBundle onLoadConfig(int, @Nullable android.service.carrier.CarrierIdentifier);
field public static final String CARRIER_SERVICE_INTERFACE = "android.service.carrier.CarrierService";
}
@@ -42571,6 +42580,7 @@
method @Deprecated public int getMnc();
method @Nullable public String getMncString();
method public String getNumber();
+ method public int getPortIndex();
method public int getSimSlotIndex();
method public int getSubscriptionId();
method public int getSubscriptionType();
@@ -42847,6 +42857,7 @@
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState(boolean, boolean);
method @Nullable public android.telephony.SignalStrength getSignalStrength();
method public int getSimCarrierId();
method @Nullable public CharSequence getSimCarrierIdName();
@@ -42899,8 +42910,10 @@
method public boolean isWorldPhone();
method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
+ method public void registerTelephonyCallback(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(boolean, @NonNull android.telephony.NetworkScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyScanManager.NetworkScanCallback);
method public void sendDialerSpecialCode(String);
method public String sendEnvelopeWithStatus(String);
method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
@@ -43120,14 +43133,28 @@
method public int describeContents();
method public int getCardId();
method @Nullable public String getEid();
- method @Nullable public String getIccId();
- method public int getSlotIndex();
+ method @Deprecated @Nullable public String getIccId();
+ method public int getPhysicalSlotIndex();
+ method @NonNull public java.util.Collection<android.telephony.UiccPortInfo> getPorts();
+ method @Deprecated public int getSlotIndex();
method public boolean isEuicc();
+ method public boolean isMultipleEnabledProfilesSupported();
method public boolean isRemovable();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccCardInfo> CREATOR;
}
+ public final class UiccPortInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getIccId();
+ method @IntRange(from=0) public int getLogicalSlotIndex();
+ method @IntRange(from=0) public int getPortIndex();
+ method public boolean isActive();
+ method public void writeToParcel(@Nullable android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccPortInfo> CREATOR;
+ field public static final String ICCID_REDACTED = "FFFFFFFFFFFFFFFFFFFF";
+ }
+
public abstract class VisualVoicemailService extends android.app.Service {
ctor public VisualVoicemailService();
method public android.os.IBinder onBind(android.content.Intent);
@@ -43428,6 +43455,7 @@
method @Nullable public String getEid();
method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo();
method public boolean isEnabled();
+ method public boolean isSimPortAvailable(int);
method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException;
method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent);
method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 81b4ee8..a646a68 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -113,6 +113,8 @@
public class AudioManager {
method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
+ method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp();
+ method @NonNull public java.util.List<android.bluetooth.BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio();
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BtProfileConnectionInfo);
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setA2dpSuspended(boolean);
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setBluetoothHeadsetProperties(@NonNull String, boolean, boolean);
@@ -202,6 +204,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR;
}
+ public final class IpSecManager {
+ field public static final int DIRECTION_FWD = 2; // 0x2
+ }
+
public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
method public int getResourceId();
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index cd1fef9..9bca7fc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -66,6 +66,7 @@
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+ field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
field public static final String BRICK = "android.permission.BRICK";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
@@ -5251,6 +5252,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
+ method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int);
method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
@@ -5840,9 +5842,11 @@
method @Nullable @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, @NonNull android.media.tv.TvInputInfo, @Nullable String, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.TvInputManager.HardwareCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void addBlockedRating(@NonNull android.media.tv.TvContentRating);
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
+ method @NonNull public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String);
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
+ method @Nullable public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList();
method @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS) public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList();
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean isSingleSessionActive();
@@ -5873,6 +5877,9 @@
public abstract class TvInputService extends android.app.Service {
method @Nullable public android.os.IBinder createExtension();
+ method @NonNull public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames();
+ method @Nullable public android.os.IBinder getExtensionInterface(@NonNull String);
+ method @Nullable public String getExtensionInterfacePermission(@NonNull String);
method @Nullable public android.media.tv.TvInputInfo onHardwareAdded(android.media.tv.TvInputHardwareInfo);
method @Nullable public String onHardwareRemoved(android.media.tv.TvInputHardwareInfo);
method @Nullable public android.media.tv.TvInputInfo onHdmiDeviceAdded(android.hardware.hdmi.HdmiDeviceInfo);
@@ -9447,7 +9454,9 @@
field public static final int MATCH_ALL_APN_SET_ID = -1; // 0xffffffff
field public static final String MAX_CONNECTIONS = "max_conns";
field public static final String MODEM_PERSIST = "modem_cognitive";
- field public static final String MTU = "mtu";
+ field @Deprecated public static final String MTU = "mtu";
+ field public static final String MTU_V4 = "mtu_v4";
+ field public static final String MTU_V6 = "mtu_v6";
field public static final int NO_APN_SET_ID = 0; // 0x0
field public static final String TIME_LIMIT_FOR_MAX_CONNECTIONS = "max_conns_time";
field public static final int UNEDITED = 0; // 0x0
@@ -11697,6 +11706,14 @@
field public static final int ROAMING_TYPE_UNKNOWN = 1; // 0x1
}
+ public final class SignalStrengthUpdateRequest implements android.os.Parcelable {
+ method public boolean isSystemThresholdReportingRequestedWhileIdle();
+ }
+
+ public static final class SignalStrengthUpdateRequest.Builder {
+ method @NonNull @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public android.telephony.SignalStrengthUpdateRequest.Builder setSystemThresholdReportingRequestedWhileIdle(boolean);
+ }
+
public final class SmsCbCmasInfo implements android.os.Parcelable {
ctor public SmsCbCmasInfo(int, int, int, int, int, int);
method public int describeContents();
@@ -11992,6 +12009,7 @@
}
public class TelephonyManager {
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addCarrierPrivilegesListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) @WorkerThread public void bootstrapAuthenticationRequest(int, @NonNull android.net.Uri, @NonNull android.telephony.gba.UaSecurityProtocolIdentifier, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.BootstrapAuthenticationCallback);
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
@@ -12016,6 +12034,8 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCarrierPrivilegeStatus(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCarrierPrivilegedPackagesForAllActiveSubscriptions();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierRestrictionRules getCarrierRestrictionRules();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getCarrierServicePackageName();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getCarrierServicePackageNameForLogicalSlot(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaEnhancedRoamingIndicatorDisplayNumber();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn(int);
@@ -12084,6 +12104,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeCarrierPrivilegesListener(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
@@ -12120,6 +12141,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimSlotMapping(@NonNull java.util.Collection<android.telephony.UiccSlotMapping>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
@@ -12131,7 +12153,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff();
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
method public void updateServiceLocation();
@@ -12259,6 +12281,10 @@
field public static final int RESULT_SUCCESS = 0; // 0x0
}
+ public static interface TelephonyManager.CarrierPrivilegesListener {
+ method public void onCarrierPrivilegesChanged(@NonNull java.util.List<java.lang.String>, @NonNull int[]);
+ }
+
public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
method public int getErrorCode();
field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2
@@ -12301,10 +12327,11 @@
method public int describeContents();
method public String getCardId();
method public int getCardStateInfo();
- method public boolean getIsActive();
+ method @Deprecated public boolean getIsActive();
method public boolean getIsEuicc();
method public boolean getIsExtendedApduSupported();
- method public int getLogicalSlotIdx();
+ method @Deprecated public int getLogicalSlotIdx();
+ method @NonNull public java.util.Collection<android.telephony.UiccPortInfo> getPorts();
method public boolean isRemovable();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CARD_STATE_INFO_ABSENT = 1; // 0x1
@@ -12314,6 +12341,15 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccSlotInfo> CREATOR;
}
+ public final class UiccSlotMapping implements android.os.Parcelable {
+ method public int describeContents();
+ method @IntRange(from=0) public int getLogicalSlotIndex();
+ method @IntRange(from=0) public int getPhysicalSlotIndex();
+ method @IntRange(from=0) public int getPortIndex();
+ method public void writeToParcel(@Nullable android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccSlotMapping> CREATOR;
+ }
+
public abstract class VisualVoicemailService extends android.app.Service {
method public static final void sendVisualVoicemailSms(android.content.Context, android.telecom.PhoneAccountHandle, String, short, String, android.app.PendingIntent);
method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
@@ -12496,6 +12532,7 @@
method public final int getSlotIndex();
method public final void notifyApnUnthrottled(@NonNull String);
method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
+ method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback);
@@ -12506,6 +12543,7 @@
public class DataServiceCallback {
method public void onApnUnthrottled(@NonNull String);
method public void onDataCallListChanged(@NonNull java.util.List<android.telephony.data.DataCallResponse>);
+ method public void onDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
method public void onDeactivateDataCallComplete(int);
method public void onRequestDataCallListComplete(int, @NonNull java.util.List<android.telephony.data.DataCallResponse>);
method public void onSetDataProfileComplete(int);
@@ -12605,7 +12643,8 @@
method public void authenticateServer(String, String, byte[], byte[], byte[], byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
method public void cancelSession(String, byte[], @android.telephony.euicc.EuiccCardManager.CancelReason int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
method public void deleteProfile(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
- method public void disableProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
+ method @Deprecated public void disableProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
+ method public void disableProfile(@Nullable String, @Nullable String, int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
method public void listNotifications(String, @android.telephony.euicc.EuiccNotification.Event int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>);
method public void loadBoundProfilePackage(String, byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
method public void prepareDownload(String, @Nullable byte[], byte[], byte[], byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
@@ -12623,7 +12662,8 @@
method public void retrieveNotificationList(String, @android.telephony.euicc.EuiccNotification.Event int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>);
method public void setDefaultSmdpAddress(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
method public void setNickname(String, String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
- method public void switchToProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>);
+ method @Deprecated public void switchToProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>);
+ method public void switchToProfile(@Nullable String, @Nullable String, int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>);
field public static final int CANCEL_REASON_END_USER_REJECTED = 0; // 0x0
field public static final int CANCEL_REASON_POSTPONED = 1; // 0x1
field public static final int CANCEL_REASON_PPR_NOT_ALLOWED = 3; // 0x3
@@ -13489,12 +13529,14 @@
}
public final class RcsClientConfiguration implements android.os.Parcelable {
- ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
+ ctor @Deprecated public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
+ ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String, boolean);
method public int describeContents();
method @NonNull public String getClientVendor();
method @NonNull public String getClientVersion();
method @NonNull public String getRcsProfile();
method @NonNull public String getRcsVersion();
+ method public boolean isRcsEnabledByUser();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsClientConfiguration> CREATOR;
field public static final String RCS_PROFILE_1_0 = "UP_1.0";
@@ -13631,6 +13673,7 @@
field public static final int PUBLISH_STATE_NOT_PUBLISHED = 2; // 0x2
field public static final int PUBLISH_STATE_OK = 1; // 0x1
field public static final int PUBLISH_STATE_OTHER_ERROR = 6; // 0x6
+ field public static final int PUBLISH_STATE_PUBLISHING = 7; // 0x7
field public static final int PUBLISH_STATE_RCS_PROVISION_ERROR = 4; // 0x4
field public static final int PUBLISH_STATE_REQUEST_TIMEOUT = 5; // 0x5
field public static final int PUBLISH_STATE_VOICE_PROVISION_ERROR = 3; // 0x3
@@ -13893,6 +13936,7 @@
package android.telephony.ims.stub {
public interface CapabilityExchangeEventListener {
+ method public default void onPublishUpdated(int, @NonNull String, int, @NonNull String) throws android.telephony.ims.ImsException;
method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
method public void onUnpublish() throws android.telephony.ims.ImsException;
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ca9a468..343830a 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -399,6 +399,21 @@
],
}
+aidl_interface {
+ name: "android.os.statsbootstrap_aidl",
+ unstable: true,
+ srcs: [
+ "android/os/IStatsBootstrapAtomService.aidl",
+ "android/os/StatsBootstrapAtom.aidl",
+ "android/os/StatsBootstrapAtomValue.aidl",
+ ],
+ backend: {
+ cpp: {
+ enabled: true,
+ },
+ },
+}
+
// utility classes statically linked into wifi-service
filegroup {
name: "framework-wifi-service-shared-srcs",
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4376d22..f53c5b6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -771,6 +771,11 @@
return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}
+ /** @hide Should this process state be considered in the cache? */
+ public static final boolean isProcStateCached(int procState) {
+ return procState >= PROCESS_STATE_CACHED_ACTIVITY;
+ }
+
/** @hide Is this a foreground service type? */
public static boolean isForegroundService(int procState) {
return procState == PROCESS_STATE_FOREGROUND_SERVICE;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0d68df4..4e8480c 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -156,6 +156,7 @@
/**
* Update information about which app IDs are on the temp allowlist.
* @param appids the updated list of appIds in temp allowlist.
+ * If null, it is to update only changingUid.
* @param changingUid uid to add or remove to temp allowlist.
* @param adding true to add to temp allowlist, false to remove from temp allowlist.
* @param durationMs when adding is true, the duration to be in temp allowlist.
@@ -165,7 +166,7 @@
* @param callingUid the callingUid that setup this temp allowlist, only valid when param adding
* is true.
*/
- public abstract void updateDeviceIdleTempAllowlist(int[] appids, int changingUid,
+ public abstract void updateDeviceIdleTempAllowlist(@Nullable int[] appids, int changingUid,
boolean adding, long durationMs, @TempAllowListType int type,
@ReasonCode int reasonCode,
@Nullable String reason, int callingUid);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bfff27a..3b0a5f3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4906,7 +4906,8 @@
Slog.w(TAG, "Activity top position already set to onTop=" + onTop);
return;
}
- throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
+ // TODO(b/197484331): Remove this short-term workaround while fixing the binder failure.
+ Slog.e(TAG, "Activity top position already set to onTop=" + onTop);
}
r.isTopResumedActivity = onTop;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c36b585..9272e45 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2735,10 +2735,13 @@
// need to override their display in ResourcesManager.
baseContext.mForceDisplayOverrideInResources = false;
baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
- baseContext.mDisplay = display;
final Resources windowContextResources = createWindowContextResources(baseContext);
baseContext.setResources(windowContextResources);
+ // Associate the display with window context resources so that configuration update from
+ // the server side will also apply to the display's metrics.
+ baseContext.mDisplay = ResourcesManager.getInstance()
+ .getAdjustedDisplay(display.getDisplayId(), windowContextResources);
return baseContext;
}
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 5964f71..babeb08 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -135,6 +135,7 @@
throw e.rethrowFromSystemServer();
}
}
+
/**
* Returns a list of supported game modes for a given package.
* <p>
@@ -151,4 +152,20 @@
}
}
+ /**
+ * Returns if ANGLE is enabled for a given package.
+ * <p>
+ * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+ *
+ * @hide
+ */
+ @UserHandleAware
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+ public @GameMode boolean isAngleEnabled(@NonNull String packageName) {
+ try {
+ return mService.getAngleEnabled(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 4bf8a3f..189f0a2 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -23,4 +23,5 @@
int getGameMode(String packageName, int userId);
void setGameMode(String packageName, int gameMode, int userId);
int[] getAvailableGameModes(String packageName);
+ boolean getAngleEnabled(String packageName, int userId);
}
\ No newline at end of file
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4b054f4..61b1abe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1884,6 +1884,14 @@
* clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
* activity itself.
*
+ * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
+ * both are displayed or required, depends on where and how the action is used, and the
+ * {@link Style} applied to the Notification.
+ *
+ * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
+ * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
+ * with an altered in luminance to ensure proper contrast within the Notification.
+ *
* @param icon icon to show for this action
* @param title the title of the action
* @param intent the {@link PendingIntent} to fire when users trigger this action
@@ -5387,8 +5395,8 @@
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// Use different highlighted colors for conversations' unread count
if (p.mHighlightExpander) {
- pillColor = Colors.flattenAlpha(getPrimaryAccentColor(p), bgColor);
- textColor = Colors.flattenAlpha(bgColor, pillColor);
+ pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
+ textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -6121,21 +6129,22 @@
if (emphasizedMode) {
// change the background bgColor
CharSequence title = action.title;
- ColorStateList[] outResultColor = new ColorStateList[1];
int buttonFillColor = getColors(p).getSecondaryAccentColor();
if (isLegacy()) {
title = ContrastColorUtil.clearColorSpans(title);
} else {
- int notifBackgroundColor = getColors(p).getBackgroundColor();
- title = ensureColorSpanContrast(title, notifBackgroundColor, outResultColor);
+ // Check for a full-length span color to use as the button fill color.
+ Integer fullLengthColor = getFullLengthSpanColor(title);
+ if (fullLengthColor != null) {
+ // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
+ int notifBackgroundColor = getColors(p).getBackgroundColor();
+ buttonFillColor = ensureButtonFillContrast(
+ fullLengthColor, notifBackgroundColor);
+ }
+ // Remove full-length color spans and ensure text contrast with the button fill.
+ title = ensureColorSpanContrast(title, buttonFillColor);
}
button.setTextViewText(R.id.action0, processTextSpans(title));
- boolean hasColorOverride = outResultColor[0] != null;
- if (hasColorOverride) {
- // There's a span spanning the full text, let's take it and use it as the
- // background color
- buttonFillColor = outResultColor[0].getDefaultColor();
- }
final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
buttonFillColor, mInNightMode);
button.setTextColor(R.id.action0, textColor);
@@ -6168,17 +6177,58 @@
}
/**
- * Ensures contrast on color spans against a background color. also returns the color of the
- * text if a span was found that spans over the whole text.
+ * Extract the color from a full-length span from the text.
+ *
+ * @param charSequence the charSequence containing spans
+ * @return the raw color of the text's last full-length span containing a color, or null if
+ * no full-length span sets the text color.
+ * @hide
+ */
+ @VisibleForTesting
+ @Nullable
+ public static Integer getFullLengthSpanColor(CharSequence charSequence) {
+ // NOTE: this method preserves the functionality that for a CharSequence with multiple
+ // full-length spans, the color of the last one is used.
+ Integer result = null;
+ if (charSequence instanceof Spanned) {
+ Spanned ss = (Spanned) charSequence;
+ Object[] spans = ss.getSpans(0, ss.length(), Object.class);
+ // First read through all full-length spans to get the button fill color, which will
+ // be used as the background color for ensuring contrast of non-full-length spans.
+ for (Object span : spans) {
+ int spanStart = ss.getSpanStart(span);
+ int spanEnd = ss.getSpanEnd(span);
+ boolean fullLength = (spanEnd - spanStart) == charSequence.length();
+ if (!fullLength) {
+ continue;
+ }
+ if (span instanceof TextAppearanceSpan) {
+ TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
+ ColorStateList textColor = originalSpan.getTextColor();
+ if (textColor != null) {
+ result = textColor.getDefaultColor();
+ }
+ } else if (span instanceof ForegroundColorSpan) {
+ ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
+ result = originalSpan.getForegroundColor();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Ensures contrast on color spans against a background color.
+ * Note that any full-length color spans will be removed instead of being contrasted.
*
* @param charSequence the charSequence on which the spans are
* @param background the background color to ensure the contrast against
- * @param outResultColor an array in which a color will be returned as the first element if
- * there exists a full length color span.
* @return the contrasted charSequence
+ * @hide
*/
- private static CharSequence ensureColorSpanContrast(CharSequence charSequence,
- int background, ColorStateList[] outResultColor) {
+ @VisibleForTesting
+ public static CharSequence ensureColorSpanContrast(CharSequence charSequence,
+ int background) {
if (charSequence instanceof Spanned) {
Spanned ss = (Spanned) charSequence;
Object[] spans = ss.getSpans(0, ss.length(), Object.class);
@@ -6195,19 +6245,19 @@
TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
ColorStateList textColor = originalSpan.getTextColor();
if (textColor != null) {
- int[] colors = textColor.getColors();
- int[] newColors = new int[colors.length];
- for (int i = 0; i < newColors.length; i++) {
- boolean isBgDark = isColorDark(background);
- newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
- colors[i], background, isBgDark);
- }
- textColor = new ColorStateList(textColor.getStates().clone(),
- newColors);
if (fullLength) {
- outResultColor[0] = textColor;
// Let's drop the color from the span
textColor = null;
+ } else {
+ int[] colors = textColor.getColors();
+ int[] newColors = new int[colors.length];
+ for (int i = 0; i < newColors.length; i++) {
+ boolean isBgDark = isColorDark(background);
+ newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
+ colors[i], background, isBgDark);
+ }
+ textColor = new ColorStateList(textColor.getStates().clone(),
+ newColors);
}
resultSpan = new TextAppearanceSpan(
originalSpan.getFamily(),
@@ -6217,15 +6267,14 @@
originalSpan.getLinkTextColor());
}
} else if (resultSpan instanceof ForegroundColorSpan) {
- ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
- int foregroundColor = originalSpan.getForegroundColor();
- boolean isBgDark = isColorDark(background);
- foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
- foregroundColor, background, isBgDark);
if (fullLength) {
- outResultColor[0] = ColorStateList.valueOf(foregroundColor);
resultSpan = null;
} else {
+ ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
+ int foregroundColor = originalSpan.getForegroundColor();
+ boolean isBgDark = isColorDark(background);
+ foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
+ foregroundColor, background, isBgDark);
resultSpan = new ForegroundColorSpan(foregroundColor);
}
} else {
@@ -6247,13 +6296,29 @@
*
* @param color the color to check
* @return true if the color has higher contrast with white than black
+ * @hide
*/
- private static boolean isColorDark(int color) {
+ public static boolean isColorDark(int color) {
// as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
}
/**
+ * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
+ * as the original color, but is lightened or darkened depending on whether the background
+ * is dark or light.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static int ensureButtonFillContrast(int color, int bg) {
+ return isColorDark(bg)
+ ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3)
+ : ContrastColorUtil.findContrastColor(color, bg, true, 1.3);
+ }
+
+
+ /**
* @return Whether we are currently building a notification from a legacy (an app that
* doesn't create material notifications by itself) app.
*/
@@ -6441,25 +6506,34 @@
if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
&& !styleDisplaysCustomViewInline()) {
- if (mN.contentView == null) {
- mN.contentView = createContentView();
+ RemoteViews newContentView = mN.contentView;
+ RemoteViews newBigContentView = mN.bigContentView;
+ RemoteViews newHeadsUpContentView = mN.headsUpContentView;
+ if (newContentView == null) {
+ newContentView = createContentView();
mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
- mN.contentView.getSequenceNumber());
+ newContentView.getSequenceNumber());
}
- if (mN.bigContentView == null) {
- mN.bigContentView = createBigContentView();
- if (mN.bigContentView != null) {
+ if (newBigContentView == null) {
+ newBigContentView = createBigContentView();
+ if (newBigContentView != null) {
mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
- mN.bigContentView.getSequenceNumber());
+ newBigContentView.getSequenceNumber());
}
}
- if (mN.headsUpContentView == null) {
- mN.headsUpContentView = createHeadsUpContentView();
- if (mN.headsUpContentView != null) {
+ if (newHeadsUpContentView == null) {
+ newHeadsUpContentView = createHeadsUpContentView();
+ if (newHeadsUpContentView != null) {
mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
- mN.headsUpContentView.getSequenceNumber());
+ newHeadsUpContentView.getSequenceNumber());
}
}
+ // Don't set any of the content views until after they have all been generated,
+ // to avoid the generated .contentView triggering the logic which skips generating
+ // the .bigContentView.
+ mN.contentView = newContentView;
+ mN.bigContentView = newBigContentView;
+ mN.headsUpContentView = newHeadsUpContentView;
}
if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
@@ -12305,6 +12379,8 @@
private int mSecondaryTextColor = COLOR_INVALID;
private int mPrimaryAccentColor = COLOR_INVALID;
private int mSecondaryAccentColor = COLOR_INVALID;
+ private int mTertiaryAccentColor = COLOR_INVALID;
+ private int mOnAccentTextColor = COLOR_INVALID;
private int mErrorColor = COLOR_INVALID;
private int mContrastColor = COLOR_INVALID;
private int mRippleAlpha = 0x33;
@@ -12362,7 +12438,7 @@
if (isColorized) {
if (rawColor == COLOR_DEFAULT) {
- int[] attrs = {R.attr.colorAccentTertiary};
+ int[] attrs = {R.attr.colorAccentSecondary};
try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
mBackgroundColor = getColor(ta, 0, Color.WHITE);
}
@@ -12379,6 +12455,8 @@
mContrastColor = mPrimaryTextColor;
mPrimaryAccentColor = mPrimaryTextColor;
mSecondaryAccentColor = mSecondaryTextColor;
+ mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
+ mOnAccentTextColor = mBackgroundColor;
mErrorColor = mPrimaryTextColor;
mRippleAlpha = 0x33;
} else {
@@ -12389,6 +12467,8 @@
R.attr.textColorSecondary,
R.attr.colorAccent,
R.attr.colorAccentSecondary,
+ R.attr.colorAccentTertiary,
+ R.attr.textColorOnAccent,
R.attr.colorError,
R.attr.colorControlHighlight
};
@@ -12399,8 +12479,10 @@
mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
- mErrorColor = getColor(ta, 6, COLOR_INVALID);
- mRippleAlpha = Color.alpha(getColor(ta, 7, 0x33ffffff));
+ mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
+ mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
+ mErrorColor = getColor(ta, 8, COLOR_INVALID);
+ mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
}
mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
mBackgroundColor, nightMode);
@@ -12420,6 +12502,14 @@
if (mSecondaryAccentColor == COLOR_INVALID) {
mSecondaryAccentColor = mContrastColor;
}
+ if (mTertiaryAccentColor == COLOR_INVALID) {
+ mTertiaryAccentColor = mContrastColor;
+ }
+ if (mOnAccentTextColor == COLOR_INVALID) {
+ mOnAccentTextColor = ColorUtils.setAlphaComponent(
+ ContrastColorUtil.resolvePrimaryColor(
+ ctx, mTertiaryAccentColor, nightMode), 0xFF);
+ }
if (mErrorColor == COLOR_INVALID) {
mErrorColor = mPrimaryTextColor;
}
@@ -12485,6 +12575,16 @@
return mSecondaryAccentColor;
}
+ /** @return the theme's tertiary accent color for colored UI elements. */
+ public @ColorInt int getTertiaryAccentColor() {
+ return mTertiaryAccentColor;
+ }
+
+ /** @return the theme's text color to be used on the tertiary accent color. */
+ public @ColorInt int getOnAccentTextColor() {
+ return mOnAccentTextColor;
+ }
+
/**
* @return the contrast-adjusted version of the color provided by the app, or the
* primary text color when colorized.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1837fb8..6553b61 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -260,8 +260,6 @@
private boolean mDemoted = false;
private boolean mImportantConvo = false;
private long mDeletedTime = DEFAULT_DELETION_TIME_MS;
- // If the sound for this channel is missing, e.g. after restore.
- private boolean mIsSoundMissing;
/**
* Creates a notification channel.
@@ -717,13 +715,6 @@
}
/**
- * @hide
- */
- public boolean isSoundMissing() {
- return mIsSoundMissing;
- }
-
- /**
* Returns the audio attributes for sound played by notifications posted to this channel.
*/
public AudioAttributes getAudioAttributes() {
@@ -1007,9 +998,8 @@
// according to the docs because canonicalize method has to handle canonical uris as well.
Uri canonicalizedUri = contentResolver.canonicalize(uri);
if (canonicalizedUri == null) {
- // We got a null because the uri in the backup does not exist here.
- mIsSoundMissing = true;
- return null;
+ // We got a null because the uri in the backup does not exist here, so we return default
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
}
return contentResolver.uncanonicalize(canonicalizedUri);
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 0171f0a..f9739a4 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -130,6 +130,7 @@
import android.net.IEthernetManager;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
import android.net.IPacProxyManager;
import android.net.IVpnManager;
import android.net.IpSecManager;
@@ -981,7 +982,11 @@
new CachedServiceFetcher<NetworkStatsManager>() {
@Override
public NetworkStatsManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- return new NetworkStatsManager(ctx.getOuterContext());
+ // TODO: Replace with an initializer in the module, see
+ // {@code ConnectivityFrameworkInitializer}.
+ final INetworkStatsService service = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE));
+ return new NetworkStatsManager(ctx.getOuterContext(), service);
}});
registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class,
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8d332ab..6cfa39c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -559,6 +559,53 @@
return null;
}
+ public Rect peekWallpaperDimensions(Context context, boolean returnDefault, int userId) {
+ if (mService != null) {
+ try {
+ if (!mService.isWallpaperSupported(context.getOpPackageName())) {
+ return new Rect();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ Rect dimensions = null;
+ synchronized (this) {
+ try {
+ Bundle params = new Bundle();
+ // Let's peek user wallpaper first.
+ ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
+ context.getOpPackageName(), context.getAttributionTag(), this,
+ FLAG_SYSTEM, params, userId);
+ if (pfd != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options);
+ dimensions = new Rect(0, 0, options.outWidth, options.outHeight);
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "peek wallpaper dimensions failed", ex);
+ }
+ }
+ // If user wallpaper is unavailable, may be the default one instead.
+ if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
+ && returnDefault) {
+ InputStream is = openDefaultWallpaper(context, FLAG_SYSTEM);
+ if (is != null) {
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, options);
+ dimensions = new Rect(0, 0, options.outWidth, options.outHeight);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+ }
+ return dimensions;
+ }
+
void forgetLoadedWallpaper() {
synchronized (this) {
mCachedWallpaper = null;
@@ -1039,6 +1086,17 @@
}
/**
+ * Peek the dimensions of system wallpaper of the user without decoding it.
+ *
+ * @return the dimensions of system wallpaper
+ * @hide
+ */
+ public Rect peekBitmapDimensions() {
+ return sGlobals.peekWallpaperDimensions(
+ mContext, true /* returnDefault */, mContext.getUserId());
+ }
+
+ /**
* Get an open, readable file descriptor to the given wallpaper image file.
* The caller is responsible for closing the file descriptor when done ingesting the file.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0e04ad3..0fe80c4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9734,6 +9734,27 @@
}
/**
+ * @param userId The user for whom to retrieve information.
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @return Details of admin and user which enforced the restriction for the userId. If
+ * restriction is null, profile owner for the user or device owner info is returned.
+ * @hide
+ */
+ public @Nullable Bundle getEnforcingAdminAndUserDetails(int userId,
+ @Nullable String restriction) {
+ if (mService != null) {
+ try {
+ return mService.getEnforcingAdminAndUserDetails(userId, restriction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
* actual package file remain. This function can be called by a device owner, profile owner, or
* by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fdc4a16..d287437 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -251,6 +251,7 @@
boolean isNotificationListenerServicePermitted(in String packageName, int userId);
Intent createAdminSupportIntent(in String restriction);
+ Bundle getEnforcingAdminAndUserDetails(int userId,String restriction);
boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent);
boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent);
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index dae565e..67f631f 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -281,6 +281,32 @@
}
/**
+ * Convenience method for callers who need to indicate that some other package or
+ * some other user needs a backup pass. This can be useful in the case of groups of
+ * packages that share a uid and/or have user-specific data.
+ * <p>
+ * This method requires that the application hold the "android.permission.BACKUP"
+ * permission if the package named in the package argument does not run under the
+ * same uid as the caller. This method also requires that the application hold the
+ * "android.permission.INTERACT_ACROSS_USERS_FULL" if the user argument is not the
+ * same as the user the caller is running under.
+ * @param userId The user to back up
+ * @param packageName The package name identifying the application to back up.
+ *
+ * @hide
+ */
+ public static void dataChangedForUser(int userId, String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChangedForUser(userId, packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "dataChanged(userId,pkg) couldn't connect");
+ }
+ }
+ }
+
+ /**
* @deprecated Applications shouldn't request a restore operation using this method. In Android
* P and later, this method is a no-op.
*
diff --git a/core/java/android/app/cloudsearch/OWNERS b/core/java/android/app/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/core/java/android/app/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java
index c64e107..f073b4e 100644
--- a/core/java/android/app/search/Query.java
+++ b/core/java/android/app/search/Query.java
@@ -70,7 +70,7 @@
@NonNull Bundle extras) {
mInput = input;
mTimestampMillis = timestampMillis;
- mExtras = extras == null ? extras : new Bundle();
+ mExtras = extras != null ? extras : new Bundle();
}
/**
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 5143579..8aa2785 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -552,11 +552,6 @@
inflateAsync(rvToApply);
return;
}
-
- // Prepare a local reference to the remote Context so we're ready to
- // inflate any requested LayoutParams
- mRemoteContext = getRemoteContext();
-
int layoutId = rvToApply.getLayoutId();
if (rvToApply.canRecycleView(mView)) {
try {
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 063ba11..2e94dd1 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -143,7 +143,7 @@
public ComponentName provider;
/**
- * The default height of the widget when added to a host, in dp. The widget will get
+ * The default height of the widget when added to a host, in px. The widget will get
* at least this width, and will often be given more, depending on the host.
*
* <p>This field corresponds to the <code>android:minWidth</code> attribute in
@@ -152,7 +152,7 @@
public int minWidth;
/**
- * The default height of the widget when added to a host, in dp. The widget will get
+ * The default height of the widget when added to a host, in px. The widget will get
* at least this height, and will often be given more, depending on the host.
*
* <p>This field corresponds to the <code>android:minHeight</code> attribute in
@@ -161,7 +161,7 @@
public int minHeight;
/**
- * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
+ * Minimum width (in px) which the widget can be resized to. This field has no effect if it
* is greater than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:minResizeWidth</code> attribute in
@@ -170,7 +170,7 @@
public int minResizeWidth;
/**
- * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
+ * Minimum height (in px) which the widget can be resized to. This field has no effect if it
* is greater than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:minResizeHeight</code> attribute in
@@ -179,7 +179,7 @@
public int minResizeHeight;
/**
- * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+ * Maximum width (in px) which the widget can be resized to. This field has no effect if it is
* smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
@@ -189,7 +189,7 @@
public int maxResizeWidth;
/**
- * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+ * Maximum height (in px) which the widget can be resized to. This field has no effect if it is
* smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
diff --git a/core/java/android/bluetooth/Attributable.java b/core/java/android/bluetooth/Attributable.java
new file mode 100644
index 0000000..d9acbe3
--- /dev/null
+++ b/core/java/android/bluetooth/Attributable.java
@@ -0,0 +1,55 @@
+/*
+ * 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 android.bluetooth;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.AttributionSource;
+
+import java.util.List;
+
+/**
+ * Marker interface for a class which can have an {@link AttributionSource}
+ * assigned to it; these are typically {@link android.os.Parcelable} classes
+ * which need to be updated after crossing Binder transaction boundaries.
+ *
+ * @hide
+ */
+public interface Attributable {
+ void setAttributionSource(@NonNull AttributionSource attributionSource);
+
+ static @Nullable <T extends Attributable> T setAttributionSource(
+ @Nullable T attributable,
+ @NonNull AttributionSource attributionSource) {
+ if (attributable != null) {
+ attributable.setAttributionSource(attributionSource);
+ }
+ return attributable;
+ }
+
+ static @Nullable <T extends Attributable> List<T> setAttributionSource(
+ @Nullable List<T> attributableList,
+ @NonNull AttributionSource attributionSource) {
+ if (attributableList != null) {
+ final int size = attributableList.size();
+ for (int i = 0; i < size; i++) {
+ setAttributionSource(attributableList.get(i), attributionSource);
+ }
+ }
+ return attributableList;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 1dd32fe..8b9cec1 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -16,7 +16,8 @@
package android.bluetooth;
-import android.Manifest;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,20 +30,21 @@
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
@@ -273,7 +275,7 @@
IBluetoothA2dp.class.getName()) {
@Override
public IBluetoothA2dp getServiceInterface(IBinder service) {
- return IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothA2dp.Stub.asInterface(service);
}
};
@@ -324,17 +326,21 @@
@UnsupportedAppUsage
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- return service.connect(device);
+ final IBluetoothA2dp service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connectWithAttribution(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -366,17 +372,21 @@
@UnsupportedAppUsage
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- return service.disconnect(device);
+ final IBluetoothA2dp service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnectWithAttribution(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -387,19 +397,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
+ final IBluetoothA2dp service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevicesWithAttribution(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevicesWithAttribution(mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
}
+ return defaultValue;
}
/**
@@ -410,20 +425,25 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
+ final IBluetoothA2dp service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStatesWithAttribution(states,
+ mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStatesWithAttribution(states,
- mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
}
+ return defaultValue;
}
/**
@@ -434,18 +454,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @BtProfileState int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- return service.getConnectionState(device);
+ final IBluetoothA2dp service = getService();
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionStateWithAttribution(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
}
+ return defaultValue;
}
/**
@@ -473,18 +496,21 @@
@UnsupportedAppUsage(trackingBug = 171933273)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) log("setActiveDevice(" + device + ")");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()
- && ((device == null) || isValidDevice(device))) {
- return service.setActiveDevice(device, mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && ((device == null) || isValidDevice(device))) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setActiveDevice(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -501,18 +527,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothDevice getActiveDevice() {
if (VDBG) log("getActiveDevice()");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
+ final IBluetoothA2dp service = getService();
+ final BluetoothDevice defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<BluetoothDevice> recv =
+ new SynchronousResultReceiver();
+ service.getActiveDevice(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getActiveDevice(mAttributionSource), mAttributionSource);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return null;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return null;
}
+ return defaultValue;
}
/**
@@ -557,22 +589,23 @@
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -591,19 +624,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- return BluetoothAdapter.connectionPolicyToPriority(
- service.getPriority(device, mAttributionSource));
- }
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.PRIORITY_OFF;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.PRIORITY_OFF;
- }
+ return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
}
/**
@@ -625,18 +646,21 @@
})
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- return service.getConnectionPolicy(device, mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
+ return defaultValue;
}
/**
@@ -648,17 +672,21 @@
@RequiresNoPermission
public boolean isAvrcpAbsoluteVolumeSupported() {
if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
- return service.isAvrcpAbsoluteVolumeSupported();
+ final IBluetoothA2dp service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isAvrcpAbsoluteVolumeSupported(recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
- return false;
}
+ return defaultValue;
}
/**
@@ -671,14 +699,16 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setAvrcpAbsoluteVolume(int volume) {
if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
+ final IBluetoothA2dp service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
service.setAvrcpAbsoluteVolume(volume, mAttributionSource);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
}
}
@@ -691,18 +721,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isA2dpPlaying(BluetoothDevice device) {
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- return service.isA2dpPlaying(device, mAttributionSource);
+ if (DBG) log("isA2dpPlaying(" + device + ")");
+ final IBluetoothA2dp service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isA2dpPlaying(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -731,8 +765,7 @@
/**
* Gets the current codec status (configuration and capability).
*
- * @param device the remote Bluetooth device. If null, use the current
- * active A2DP Bluetooth device.
+ * @param device the remote Bluetooth device.
* @return the current codec status
* @hide
*/
@@ -744,26 +777,28 @@
public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
verifyDeviceNotNull(device, "getCodecStatus");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
- return service.getCodecStatus(device, mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final BluetoothCodecStatus defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<BluetoothCodecStatus> recv =
+ new SynchronousResultReceiver();
+ service.getCodecStatus(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- }
- return null;
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in getCodecStatus()", e);
- return null;
}
+ return defaultValue;
}
/**
* Sets the codec configuration preference.
*
- * @param device the remote Bluetooth device. If null, use the current
- * active A2DP Bluetooth device.
+ * @param device the remote Bluetooth device.
* @param codecConfig the codec configuration preference
* @hide
*/
@@ -779,24 +814,23 @@
Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
throw new IllegalArgumentException("codecConfig cannot be null");
}
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
+ final IBluetoothA2dp service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
service.setCodecConfigPreference(device, codecConfig, mAttributionSource);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return;
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e);
- return;
}
}
/**
* Enables the optional codecs.
*
- * @param device the remote Bluetooth device. If null, use the currect
- * active A2DP Bluetooth device.
+ * @param device the remote Bluetooth device.
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -812,8 +846,7 @@
/**
* Disables the optional codecs.
*
- * @param device the remote Bluetooth device. If null, use the currect
- * active A2DP Bluetooth device.
+ * @param device the remote Bluetooth device.
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -829,26 +862,25 @@
/**
* Enables or disables the optional codecs.
*
- * @param device the remote Bluetooth device. If null, use the currect
- * active A2DP Bluetooth device.
+ * @param device the remote Bluetooth device.
* @param enable if true, enable the optional codecs, other disable them
*/
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
+ final IBluetoothA2dp service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
if (enable) {
service.enableOptionalCodecs(device, mAttributionSource);
} else {
service.disableOptionalCodecs(device, mAttributionSource);
}
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return;
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e);
- return;
}
}
@@ -866,18 +898,23 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@OptionalCodecsSupportStatus
public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
+ if (DBG) log("isOptionalCodecsSupported(" + device + ")");
verifyDeviceNotNull(device, "isOptionalCodecsSupported");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- return service.supportsOptionalCodecs(device, mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final int defaultValue = OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.supportsOptionalCodecs(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in supportsOptionalCodecs()", e);
- return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
}
+ return defaultValue;
}
/**
@@ -894,18 +931,23 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@OptionalCodecsPreferenceStatus
public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
+ if (DBG) log("isOptionalCodecsEnabled(" + device + ")");
verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- return service.getOptionalCodecsEnabled(device, mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final int defaultValue = OPTIONAL_CODECS_PREF_UNKNOWN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getOptionalCodecsEnabled(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return OPTIONAL_CODECS_PREF_UNKNOWN;
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in getOptionalCodecsEnabled()", e);
- return OPTIONAL_CODECS_PREF_UNKNOWN;
}
+ return defaultValue;
}
/**
@@ -923,24 +965,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
@OptionalCodecsPreferenceStatus int value) {
+ if (DBG) log("setOptionalCodecsEnabled(" + device + ")");
verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
- try {
- if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
- && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
- && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
- Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
- return;
- }
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()
- && isValidDevice(device)) {
+ if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
+ && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
+ && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+ Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
+ return;
+ }
+ final IBluetoothA2dp service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
service.setOptionalCodecsEnabled(device, value, mAttributionSource);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return;
}
}
@@ -963,17 +1005,21 @@
})
public @Type int getDynamicBufferSupport() {
if (VDBG) log("getDynamicBufferSupport()");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
- return service.getDynamicBufferSupport(mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final int defaultValue = DYNAMIC_BUFFER_SUPPORT_NONE;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getDynamicBufferSupport(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return DYNAMIC_BUFFER_SUPPORT_NONE;
- } catch (RemoteException e) {
- Log.e(TAG, "failed to get getDynamicBufferSupport, error: ", e);
- return DYNAMIC_BUFFER_SUPPORT_NONE;
}
+ return defaultValue;
}
/**
@@ -994,17 +1040,22 @@
})
public @Nullable BufferConstraints getBufferConstraints() {
if (VDBG) log("getBufferConstraints()");
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
- return service.getBufferConstraints(mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final BufferConstraints defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<BufferConstraints> recv =
+ new SynchronousResultReceiver();
+ service.getBufferConstraints(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return null;
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return null;
}
+ return defaultValue;
}
/**
@@ -1029,17 +1080,21 @@
Log.e(TAG, "Trying to set audio buffer length to a negative value: " + value);
return false;
}
- try {
- final IBluetoothA2dp service = getService();
- if (service != null && isEnabled()) {
- return service.setBufferLengthMillis(codec, value, mAttributionSource);
+ final IBluetoothA2dp service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setBufferLengthMillis(codec, value, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
}
+ return defaultValue;
}
/**
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index 2dd63a0..5941681 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -16,28 +16,31 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.SdkConstant.SdkConstantType;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the public APIs to control the Bluetooth A2DP Sink
@@ -87,7 +90,7 @@
"BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) {
@Override
public IBluetoothA2dpSink getServiceInterface(IBinder service) {
- return IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothA2dpSink.Stub.asInterface(service);
}
};
@@ -141,16 +144,20 @@
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.connect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -182,16 +189,20 @@
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.disconnect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -205,17 +216,23 @@
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -229,18 +246,23 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -252,18 +274,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
- if (VDBG) log("getState(" + device + ")");
+ if (VDBG) log("getConnectionState(" + device + ")");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -283,16 +309,21 @@
public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
if (VDBG) log("getAudioConfig(" + device + ")");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final BluetoothAudioConfig defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getAudioConfig(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return null;
+ final SynchronousResultReceiver<BluetoothAudioConfig> recv =
+ new SynchronousResultReceiver();
+ service.getAudioConfig(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return null;
+ return defaultValue;
}
/**
@@ -338,20 +369,22 @@
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -394,16 +427,20 @@
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
/**
@@ -421,17 +458,22 @@
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
})
public boolean isAudioPlaying(@NonNull BluetoothDevice device) {
+ if (VDBG) log("isAudioPlaying(" + device + ")");
final IBluetoothA2dpSink service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.isA2dpPlaying(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isA2dpPlaying(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 14be921..c6c64b0 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -29,7 +29,6 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.app.ActivityThread;
import android.app.PropertyInvalidatedCache;
import android.bluetooth.BluetoothDevice.Transport;
import android.bluetooth.BluetoothProfile.ConnectionPolicy;
@@ -48,7 +47,6 @@
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Binder;
@@ -58,7 +56,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
-import android.os.SystemProperties;
+import android.sysprop.BluetoothProperties;
import android.util.Log;
import android.util.Pair;
@@ -788,7 +786,7 @@
@RequiresNoPermission
public static synchronized BluetoothAdapter getDefaultAdapter() {
if (sAdapter == null) {
- sAdapter = createAdapter(BluetoothManager.resolveAttributionSource(null));
+ sAdapter = createAdapter(AttributionSource.myAttributionSource());
}
return sAdapter;
}
@@ -991,7 +989,6 @@
if (!isBleScanAlwaysAvailable()) {
return false;
}
- String packageName = ActivityThread.currentPackageName();
try {
return mManagerService.disableBle(mAttributionSource, mToken);
} catch (RemoteException e) {
@@ -1038,7 +1035,6 @@
if (!isBleScanAlwaysAvailable()) {
return false;
}
- String packageName = ActivityThread.currentPackageName();
try {
return mManagerService.enableBle(mAttributionSource, mToken);
} catch (RemoteException e) {
@@ -1341,7 +1337,7 @@
return true;
}
Log.e(TAG, "factoryReset(): Setting persist.bluetooth.factoryreset to retry later");
- SystemProperties.set("persist.bluetooth.factoryreset", "true");
+ BluetoothProperties.factory_reset(true);
} catch (RemoteException e) {
Log.e(TAG, "", e);
} finally {
@@ -3090,6 +3086,9 @@
BluetoothCsipSetCoordinator csipSetCoordinator =
new BluetoothCsipSetCoordinator(context, listener, this);
return true;
+ } else if (profile == BluetoothProfile.LE_CALL_CONTROL) {
+ BluetoothLeCallControl tbs = new BluetoothLeCallControl(context, listener);
+ return true;
} else {
return false;
}
@@ -3192,6 +3191,10 @@
(BluetoothCsipSetCoordinator) proxy;
csipSetCoordinator.close();
break;
+ case BluetoothProfile.LE_CALL_CONTROL:
+ BluetoothLeCallControl tbs = (BluetoothLeCallControl) proxy;
+ tbs.close();
+ break;
}
}
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index d27c276..81fc3e1 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -16,22 +16,24 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
import android.annotation.SdkConstant.SdkConstantType;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
@@ -95,8 +97,7 @@
"BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) {
@Override
public IBluetoothAvrcpController getServiceInterface(IBinder service) {
- return IBluetoothAvrcpController.Stub.asInterface(
- Binder.allowBlocking(service));
+ return IBluetoothAvrcpController.Stub.asInterface(service);
}
};
@@ -132,19 +133,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
- final IBluetoothAvrcpController service =
- getService();
- if (service != null && isEnabled()) {
+ final IBluetoothAvrcpController service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -155,20 +161,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
- final IBluetoothAvrcpController service =
- getService();
- if (service != null && isEnabled()) {
+ final IBluetoothAvrcpController service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -179,18 +189,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
- final IBluetoothAvrcpController service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothAvrcpController service = getService();
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -203,17 +216,22 @@
public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getPlayerSettings");
BluetoothAvrcpPlayerSettings settings = null;
- final IBluetoothAvrcpController service =
- getService();
- if (service != null && isEnabled()) {
+ final IBluetoothAvrcpController service = getService();
+ final BluetoothAvrcpPlayerSettings defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
- settings = service.getPlayerSettings(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
- return null;
+ final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv =
+ new SynchronousResultReceiver();
+ service.getPlayerSettings(device, mAttributionSource, recv);
+ settings = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- return settings;
+ return defaultValue;
}
/**
@@ -224,18 +242,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
- final IBluetoothAvrcpController service =
- getService();
- if (service != null && isEnabled()) {
+ final IBluetoothAvrcpController service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
- return service.setPlayerApplicationSetting(plAppSetting, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -247,18 +268,20 @@
public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = "
+ keyState);
- final IBluetoothAvrcpController service =
- getService();
- if (service != null && isEnabled()) {
+ final IBluetoothAvrcpController service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
- service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource);
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
return;
- } catch (RemoteException e) {
- Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
- return;
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
}
private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
index f0a8df0..ba57ec4 100644
--- a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
+++ b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
@@ -17,6 +17,8 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -27,13 +29,14 @@
import android.annotation.SystemApi;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.CloseGuard;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -41,6 +44,7 @@
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the public APIs to control the Bluetooth CSIP set coordinator.
@@ -229,8 +233,7 @@
IBluetoothCsipSetCoordinator.class.getName()) {
@Override
public IBluetoothCsipSetCoordinator getServiceInterface(IBinder service) {
- return IBluetoothCsipSetCoordinator.Stub.asInterface(
- Binder.allowBlocking(service));
+ return IBluetoothCsipSetCoordinator.Stub.asInterface(service);
}
};
@@ -283,26 +286,27 @@
public
@Nullable UUID groupLock(int groupId, @Nullable @CallbackExecutor Executor executor,
@Nullable ClientLockCallback cb) {
- if (VDBG) {
- log("groupLockSet()");
- }
+ if (VDBG) log("groupLockSet()");
final IBluetoothCsipSetCoordinator service = getService();
- try {
- if (service != null && isEnabled()) {
- IBluetoothCsipSetCoordinatorLockCallback delegate = null;
- if ((executor != null) && (cb != null)) {
- delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb);
- }
- return service.groupLock(groupId, delegate, mAttributionSource).getUuid();
+ final UUID defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ IBluetoothCsipSetCoordinatorLockCallback delegate = null;
+ if ((executor != null) && (cb != null)) {
+ delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb);
}
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
+ try {
+ final SynchronousResultReceiver<ParcelUuid> recv = new SynchronousResultReceiver();
+ service.groupLock(groupId, delegate, mAttributionSource, recv);
+ final ParcelUuid ret = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ return ret == null ? defaultValue : ret.getUuid();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- return null;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return null;
}
+ return defaultValue;
}
/**
@@ -315,27 +319,26 @@
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean groupUnlock(@NonNull UUID lockUuid) {
- if (VDBG) {
- log("groupLockSet()");
- }
+ if (VDBG) log("groupLockSet()");
if (lockUuid == null) {
return false;
}
-
final IBluetoothCsipSetCoordinator service = getService();
- try {
- if (service != null && isEnabled()) {
- service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource);
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
return true;
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- }
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -348,22 +351,22 @@
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public @NonNull Map getGroupUuidMapByDevice(@Nullable BluetoothDevice device) {
- if (VDBG) {
- log("getGroupUuidMapByDevice()");
- }
+ if (VDBG) log("getGroupUuidMapByDevice()");
final IBluetoothCsipSetCoordinator service = getService();
- try {
- if (service != null && isEnabled()) {
- return service.getGroupUuidMapByDevice(device, mAttributionSource);
+ final Map defaultValue = new HashMap<>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Map> recv = new SynchronousResultReceiver();
+ service.getGroupUuidMapByDevice(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- }
- return new HashMap<>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new HashMap<>();
}
+ return defaultValue;
}
/**
@@ -376,22 +379,23 @@
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public @NonNull List<Integer> getAllGroupIds(@Nullable ParcelUuid uuid) {
- if (VDBG) {
- log("getAllGroupIds()");
- }
+ if (VDBG) log("getAllGroupIds()");
final IBluetoothCsipSetCoordinator service = getService();
- try {
- if (service != null && isEnabled()) {
- return service.getAllGroupIds(uuid, mAttributionSource);
+ final List<Integer> defaultValue = new ArrayList<>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<Integer>> recv =
+ new SynchronousResultReceiver();
+ service.getAllGroupIds(uuid, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- }
- return new ArrayList<Integer>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<Integer>();
}
+ return defaultValue;
}
/**
@@ -399,22 +403,23 @@
*/
@Override
public @NonNull List<BluetoothDevice> getConnectedDevices() {
- if (VDBG) {
- log("getConnectedDevices()");
- }
+ if (VDBG) log("getConnectedDevices()");
final IBluetoothCsipSetCoordinator service = getService();
- if (service != null && isEnabled()) {
- try {
- return service.getConnectedDevices(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
- }
- }
+ final List<BluetoothDevice> defaultValue = new ArrayList<>();
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -422,24 +427,24 @@
*/
@Override
public
- @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
- @NonNull int[] states) {
- if (VDBG) {
- log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")");
- }
+ @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
+ if (VDBG) log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")");
final IBluetoothCsipSetCoordinator service = getService();
- if (service != null && isEnabled()) {
- try {
- return service.getDevicesMatchingConnectionStates(states, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
- }
- }
+ final List<BluetoothDevice> defaultValue = new ArrayList<>();
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -447,24 +452,23 @@
*/
@Override
public
- @BluetoothProfile.BtProfileState int getConnectionState(
- @Nullable BluetoothDevice device) {
- if (VDBG) {
- log("getState(" + device + ")");
- }
+ @BluetoothProfile.BtProfileState int getConnectionState(@Nullable BluetoothDevice device) {
+ if (VDBG) log("getState(" + device + ")");
final IBluetoothCsipSetCoordinator service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- }
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -484,26 +488,24 @@
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(
@Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
- if (DBG) {
- log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
- }
+ if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothCsipSetCoordinator service = getService();
- try {
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- }
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -521,22 +523,22 @@
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
- if (VDBG) {
- log("getConnectionPolicy(" + device + ")");
- }
+ if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothCsipSetCoordinator service = getService();
- try {
- if (service != null && isEnabled() && isValidDevice(device)) {
- return service.getConnectionPolicy(device, mAttributionSource);
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- }
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
+ return defaultValue;
}
private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 6e918bd..93f0268 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -32,7 +32,6 @@
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.companion.AssociationRequest;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Build;
@@ -1178,7 +1177,7 @@
mAddress = address;
mAddressType = ADDRESS_TYPE_PUBLIC;
- mAttributionSource = BluetoothManager.resolveAttributionSource(null);
+ mAttributionSource = AttributionSource.myAttributionSource();
}
/** {@hide} */
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 4e7c01a..b531829 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -87,7 +87,7 @@
private static final int CONN_STATE_CLOSED = 4;
private static final int WRITE_CHARACTERISTIC_MAX_RETRIES = 5;
- private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 1000; // milliseconds
+ private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 10; // milliseconds
private List<BluetoothGattService> mServices;
@@ -1806,32 +1806,33 @@
}
/**
- * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+ * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
* with {@link BluetoothProfile#GATT} as argument
- *
* @throws UnsupportedOperationException
*/
@Override
@RequiresNoPermission
+ @Deprecated
public int getConnectionState(BluetoothDevice device) {
throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
}
/**
- * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+ * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
* with {@link BluetoothProfile#GATT} as argument
*
* @throws UnsupportedOperationException
*/
@Override
@RequiresNoPermission
+ @Deprecated
public List<BluetoothDevice> getConnectedDevices() {
throw new UnsupportedOperationException(
"Use BluetoothManager#getConnectedDevices instead.");
}
/**
- * Not supported - please use
+ * @deprecated Not supported - please use
* {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
* with {@link BluetoothProfile#GATT} as first argument
*
@@ -1839,6 +1840,7 @@
*/
@Override
@RequiresNoPermission
+ @Deprecated
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
throw new UnsupportedOperationException(
"Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index c046324..1b141c9 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -27,12 +29,10 @@
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -42,8 +42,11 @@
import android.util.CloseGuard;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* Public API for controlling the Bluetooth Headset Service. This includes both
@@ -480,16 +483,20 @@
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.connect(device);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connectWithAttribution(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -521,16 +528,20 @@
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.disconnect(device);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnectWithAttribution(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -542,18 +553,23 @@
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevicesWithAttribution(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevicesWithAttribution(mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -565,18 +581,23 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -588,16 +609,20 @@
public int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getConnectionState(" + device + ")");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionStateWithAttribution(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -624,23 +649,7 @@
})
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
- final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (priority != BluetoothProfile.PRIORITY_OFF
- && priority != BluetoothProfile.PRIORITY_ON) {
- return false;
- }
- try {
- return service.setPriority(
- device, BluetoothAdapter.priorityToConnectionPolicy(priority),
- mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
- }
- }
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
}
/**
@@ -666,20 +675,22 @@
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -699,18 +710,7 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
- final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- return BluetoothAdapter.connectionPolicyToPriority(
- service.getPriority(device, mAttributionSource));
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.PRIORITY_OFF;
- }
- }
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.PRIORITY_OFF;
+ return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
}
/**
@@ -733,16 +733,20 @@
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
/**
@@ -757,15 +761,20 @@
public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
if (DBG) log("isNoiseReductionSupported()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.isNoiseReductionSupported(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isNoiseReductionSupported(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -780,15 +789,20 @@
public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
if (DBG) log("isVoiceRecognitionSupported()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.isVoiceRecognitionSupported(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isVoiceRecognitionSupported(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -819,15 +833,20 @@
public boolean startVoiceRecognition(BluetoothDevice device) {
if (DBG) log("startVoiceRecognition()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.startVoiceRecognition(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.startVoiceRecognition(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -848,15 +867,20 @@
public boolean stopVoiceRecognition(BluetoothDevice device) {
if (DBG) log("stopVoiceRecognition()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.stopVoiceRecognition(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.stopVoiceRecognition(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -871,15 +895,20 @@
public boolean isAudioConnected(BluetoothDevice device) {
if (VDBG) log("isAudioConnected()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.isAudioConnected(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isAudioConnected(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -905,17 +934,20 @@
public int getAudioState(BluetoothDevice device) {
if (VDBG) log("getAudioState");
final IBluetoothHeadset service = mService;
- if (service != null && !isDisabled()) {
- try {
- return service.getAudioState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final int defaultValue = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (!isDisabled()) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getAudioState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -933,15 +965,17 @@
public void setAudioRouteAllowed(boolean allowed) {
if (VDBG) log("setAudioRouteAllowed");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- service.setAudioRouteAllowed(allowed, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.setAudioRouteAllowed(allowed, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
}
@@ -956,17 +990,20 @@
public boolean getAudioRouteAllowed() {
if (VDBG) log("getAudioRouteAllowed");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- return service.getAudioRouteAllowed(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.getAudioRouteAllowed(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -981,15 +1018,17 @@
public void setForceScoAudio(boolean forced) {
if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- service.setForceScoAudio(forced, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.setForceScoAudio(forced, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
}
@@ -1006,16 +1045,20 @@
public boolean isAudioOn() {
if (VDBG) log("isAudioOn()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
- return service.isAudioOn(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isAudioOn(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
-
+ return defaultValue;
}
/**
@@ -1040,18 +1083,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connectAudio() {
+ if (VDBG) log("connectAudio()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- return service.connectAudio(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connectAudio(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1069,18 +1116,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnectAudio() {
+ if (VDBG) log("disconnectAudio()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- return service.disconnectAudio(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnectAudio(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1114,17 +1165,20 @@
public boolean startScoUsingVirtualVoiceCall() {
if (DBG) log("startScoUsingVirtualVoiceCall()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- return service.startScoUsingVirtualVoiceCall(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.startScoUsingVirtualVoiceCall(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1149,17 +1203,20 @@
public boolean stopScoUsingVirtualVoiceCall() {
if (DBG) log("stopScoUsingVirtualVoiceCall()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- return service.stopScoUsingVirtualVoiceCall(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.stopScoUsingVirtualVoiceCall(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1179,16 +1236,16 @@
public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
int type, String name) {
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
}
@@ -1205,16 +1262,18 @@
public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
String number, int type) {
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- service.clccResponse(index, direction, status, mode, mpty, number, type,
- mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.clccResponse(index, direction, status, mode, mpty, number, type,
+ mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
}
@@ -1246,18 +1305,21 @@
throw new IllegalArgumentException("command is null");
}
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- return service.sendVendorSpecificResultCode(device, command, arg,
- mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- }
- }
+ final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.sendVendorSpecificResultCode(device, command, arg,
+ mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1291,17 +1353,20 @@
Log.d(TAG, "setActiveDevice: " + device);
}
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
- try {
- return service.setActiveDevice(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- }
- }
+ final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && (device == null || isValidDevice(device))) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setActiveDevice(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1317,22 +1382,25 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothDevice getActiveDevice() {
- if (VDBG) {
- Log.d(TAG, "getActiveDevice");
- }
+ if (VDBG) Log.d(TAG, "getActiveDevice");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- return Attributable.setAttributionSource(
- service.getActiveDevice(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- }
- }
+ final BluetoothDevice defaultValue = null;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<BluetoothDevice> recv =
+ new SynchronousResultReceiver();
+ service.getActiveDevice(mAttributionSource, recv);
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return null;
+ return defaultValue;
}
/**
@@ -1347,21 +1415,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean isInbandRingingEnabled() {
- if (DBG) {
- log("isInbandRingingEnabled()");
- }
+ if (DBG) log("isInbandRingingEnabled()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled()) {
- try {
- return service.isInbandRingingEnabled(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- }
- }
+ final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isInbandRingingEnabled(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1381,7 +1450,7 @@
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
- mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
+ mService = IBluetoothHeadset.Stub.asInterface(service);
mHandler.sendMessage(mHandler.obtainMessage(
MESSAGE_HEADSET_SERVICE_CONNECTED));
}
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index a5a2470..7d7a7f7 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -23,18 +25,19 @@
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* Public API to control Hands Free Profile (HFP role only).
@@ -433,7 +436,7 @@
"BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) {
@Override
public IBluetoothHeadsetClient getServiceInterface(IBinder service) {
- return IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothHeadsetClient.Stub.asInterface(service);
}
};
@@ -480,18 +483,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.connect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -508,18 +514,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.disconnect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -532,19 +541,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled()) {
+ final IBluetoothHeadsetClient service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -559,20 +573,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled()) {
+ final IBluetoothHeadsetClient service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -586,18 +604,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getConnectionState(" + device + ")");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -635,22 +656,23 @@
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -687,18 +709,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final @ConnectionPolicy int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
/**
@@ -716,17 +741,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean startVoiceRecognition(BluetoothDevice device) {
if (DBG) log("startVoiceRecognition()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.startVoiceRecognition(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.startVoiceRecognition(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -740,20 +769,23 @@
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
- public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId,
- String atCommand) {
+ public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
if (DBG) log("sendVendorSpecificCommand()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -771,17 +803,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean stopVoiceRecognition(BluetoothDevice device) {
if (DBG) log("stopVoiceRecognition()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.stopVoiceRecognition(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.stopVoiceRecognition(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -794,18 +830,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
if (DBG) log("getCurrentCalls()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final List<BluetoothHeadsetClientCall> defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
+ final SynchronousResultReceiver<List<BluetoothHeadsetClientCall>> recv =
+ new SynchronousResultReceiver();
+ service.getCurrentCalls(device, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getCurrentCalls(device, mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return null;
+ return defaultValue;
}
/**
@@ -818,17 +860,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public Bundle getCurrentAgEvents(BluetoothDevice device) {
if (DBG) log("getCurrentAgEvents()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final Bundle defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getCurrentAgEvents(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver();
+ service.getCurrentAgEvents(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return null;
+ return defaultValue;
}
/**
@@ -845,17 +891,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean acceptCall(BluetoothDevice device, int flag) {
if (DBG) log("acceptCall()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.acceptCall(device, flag, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.acceptCall(device, flag, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -869,17 +919,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean holdCall(BluetoothDevice device) {
if (DBG) log("holdCall()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.holdCall(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.holdCall(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -898,17 +952,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean rejectCall(BluetoothDevice device) {
if (DBG) log("rejectCall()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.rejectCall(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.rejectCall(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -931,17 +989,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
if (DBG) log("terminateCall()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.terminateCall(device, call, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.terminateCall(device, call, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -962,17 +1024,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean enterPrivateMode(BluetoothDevice device, int index) {
if (DBG) log("enterPrivateMode()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.enterPrivateMode(device, index, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.enterPrivateMode(device, index, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -992,17 +1058,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean explicitCallTransfer(BluetoothDevice device) {
if (DBG) log("explicitCallTransfer()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.explicitCallTransfer(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.explicitCallTransfer(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -1018,18 +1088,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
if (DBG) log("dial()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final BluetoothHeadsetClientCall defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
+ final SynchronousResultReceiver<BluetoothHeadsetClientCall> recv =
+ new SynchronousResultReceiver();
+ service.dial(device, number, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.dial(device, number, mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return null;
+ return defaultValue;
}
/**
@@ -1046,17 +1122,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sendDTMF(BluetoothDevice device, byte code) {
if (DBG) log("sendDTMF()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.sendDTMF(device, code, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.sendDTMF(device, code, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -1075,17 +1155,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean getLastVoiceTagNumber(BluetoothDevice device) {
if (DBG) log("getLastVoiceTagNumber()");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getLastVoiceTagNumber(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.getLastVoiceTagNumber(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -1098,17 +1182,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getAudioState(BluetoothDevice device) {
if (VDBG) log("getAudioState");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled()) {
+ final IBluetoothHeadsetClient service = getService();
+ final int defaultValue = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
- return service.getAudioState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getAudioState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
} else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ return defaultValue;
}
return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
}
@@ -1124,17 +1212,18 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
if (VDBG) log("setAudioRouteAllowed");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled()) {
- try {
- service.setAudioRouteAllowed(device, allowed, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final IBluetoothHeadsetClient service = getService();
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.setAudioRouteAllowed(device, allowed, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
}
@@ -1149,19 +1238,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean getAudioRouteAllowed(BluetoothDevice device) {
if (VDBG) log("getAudioRouteAllowed");
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled()) {
- try {
- return service.getAudioRouteAllowed(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.getAudioRouteAllowed(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1176,19 +1267,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connectAudio(BluetoothDevice device) {
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled()) {
- try {
- return service.connectAudio(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ if (VDBG) log("connectAudio");
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connectAudio(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1203,19 +1297,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnectAudio(BluetoothDevice device) {
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled()) {
- try {
- return service.disconnectAudio(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ if (VDBG) log("disconnectAudio");
+ final IBluetoothHeadsetClient service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnectAudio(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -1227,19 +1324,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public Bundle getCurrentAgFeatures(BluetoothDevice device) {
- final IBluetoothHeadsetClient service =
- getService();
- if (service != null && isEnabled()) {
- try {
- return service.getCurrentAgFeatures(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ if (VDBG) log("getCurrentAgFeatures");
+ final IBluetoothHeadsetClient service = getService();
+ final Bundle defaultValue = null;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver();
+ service.getCurrentAgFeatures(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return null;
+ return defaultValue;
}
private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
index 3f1ef84..032b507 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.os.Build;
import android.os.Parcel;
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index 183f4d5..339a75f 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -16,29 +16,30 @@
package android.bluetooth;
-import android.Manifest;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the public APIs to control the Hearing Aid profile.
@@ -139,7 +140,7 @@
"BluetoothHearingAid", IBluetoothHearingAid.class.getName()) {
@Override
public IBluetoothHearingAid getServiceInterface(IBinder service) {
- return IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothHearingAid.Stub.asInterface(service);
}
};
@@ -184,16 +185,20 @@
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled() && isValidDevice(device)) {
- return service.connect(device, mAttributionSource);
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -226,16 +231,20 @@
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled() && isValidDevice(device)) {
- return service.disconnect(device, mAttributionSource);
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -247,17 +256,23 @@
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
}
+ return defaultValue;
}
/**
@@ -270,18 +285,23 @@
@NonNull int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
}
+ return defaultValue;
}
/**
@@ -294,17 +314,20 @@
@NonNull BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- return service.getConnectionState(device, mAttributionSource);
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
}
+ return defaultValue;
}
/**
@@ -333,18 +356,20 @@
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) log("setActiveDevice(" + device + ")");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()
- && ((device == null) || isValidDevice(device))) {
- service.setActiveDevice(device, mAttributionSource);
- return true;
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && ((device == null) || isValidDevice(device))) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setActiveDevice(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -362,17 +387,23 @@
public @NonNull List<BluetoothDevice> getActiveDevices() {
if (VDBG) log("getActiveDevices()");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getActiveDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getActiveDevices(mAttributionSource), mAttributionSource);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<>();
}
+ return defaultValue;
}
/**
@@ -419,21 +450,22 @@
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
verifyDeviceNotNull(device, "setConnectionPolicy");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -477,17 +509,20 @@
if (VDBG) log("getConnectionPolicy(" + device + ")");
verifyDeviceNotNull(device, "getConnectionPolicy");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- return service.getConnectionPolicy(device, mAttributionSource);
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
+ return defaultValue;
}
/**
@@ -522,19 +557,18 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setVolume(int volume) {
if (DBG) Log.d(TAG, "setVolume(" + volume + ")");
-
final IBluetoothHearingAid service = getService();
- try {
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- return;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.setVolume(volume, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
-
- if (!isEnabled()) return;
-
- service.setVolume(volume, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
}
}
@@ -555,24 +589,23 @@
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
})
public long getHiSyncId(@NonNull BluetoothDevice device) {
- if (VDBG) {
- log("getHiSyncId(" + device + ")");
- }
+ if (VDBG) log("getHiSyncId(" + device + ")");
verifyDeviceNotNull(device, "getConnectionPolicy");
final IBluetoothHearingAid service = getService();
- try {
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- return HI_SYNC_ID_INVALID;
+ final long defaultValue = HI_SYNC_ID_INVALID;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Long> recv = new SynchronousResultReceiver();
+ service.getHiSyncId(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
-
- if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID;
-
- return service.getHiSyncId(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return HI_SYNC_ID_INVALID;
}
+ return defaultValue;
}
/**
@@ -586,21 +619,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getDeviceSide(BluetoothDevice device) {
- if (VDBG) {
- log("getDeviceSide(" + device + ")");
- }
+ if (VDBG) log("getDeviceSide(" + device + ")");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- return service.getDeviceSide(device, mAttributionSource);
+ final int defaultValue = SIDE_LEFT;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getDeviceSide(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return SIDE_LEFT;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return SIDE_LEFT;
}
+ return defaultValue;
}
/**
@@ -614,21 +648,22 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getDeviceMode(BluetoothDevice device) {
- if (VDBG) {
- log("getDeviceMode(" + device + ")");
- }
+ if (VDBG) log("getDeviceMode(" + device + ")");
final IBluetoothHearingAid service = getService();
- try {
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- return service.getDeviceMode(device, mAttributionSource);
+ final int defaultValue = MODE_MONAURAL;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getDeviceMode(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return MODE_MONAURAL;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return MODE_MONAURAL;
}
+ return defaultValue;
}
private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index c2744b8..44a355b 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -24,17 +26,18 @@
import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
/**
* Provides the public APIs to control the Bluetooth HID Device profile.
@@ -432,7 +435,7 @@
"BluetoothHidDevice", IBluetoothHidDevice.class.getName()) {
@Override
public IBluetoothHidDevice getServiceInterface(IBinder service) {
- return IBluetoothHidDevice.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothHidDevice.Stub.asInterface(service);
}
};
@@ -456,18 +459,23 @@
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final List<BluetoothDevice> defaultValue = new ArrayList<>();
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return new ArrayList<>();
+ return defaultValue;
}
/** {@inheritDoc} */
@@ -476,19 +484,23 @@
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
- mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final List<BluetoothDevice> defaultValue = new ArrayList<>();
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return new ArrayList<>();
+ return defaultValue;
}
/** {@inheritDoc} */
@@ -497,17 +509,20 @@
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final int defaultValue = STATE_DISCONNECTED;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -556,18 +571,21 @@
}
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource);
- result = service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = result;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource);
+ service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource, recv);
+ result = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return result;
+ return defaultValue;
}
/**
@@ -583,20 +601,21 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean unregisterApp() {
- boolean result = false;
-
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- result = service.unregisterApp(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.unregisterApp(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return result;
+ return defaultValue;
}
/**
@@ -610,20 +629,21 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
- boolean result = false;
-
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- result = service.sendReport(device, id, data, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.sendReport(device, id, data, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return result;
+ return defaultValue;
}
/**
@@ -638,20 +658,21 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
- boolean result = false;
-
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- result = service.replyReport(device, type, id, data, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.replyReport(device, type, id, data, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return result;
+ return defaultValue;
}
/**
@@ -664,20 +685,21 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean reportError(BluetoothDevice device, byte error) {
- boolean result = false;
-
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- result = service.reportError(device, error, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.reportError(device, error, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return result;
+ return defaultValue;
}
/**
@@ -690,18 +712,20 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public String getUserAppName() {
final IBluetoothHidDevice service = getService();
-
- if (service != null) {
- try {
- return service.getUserAppName(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final String defaultValue = "";
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<String> recv = new SynchronousResultReceiver();
+ service.getUserAppName(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return "";
+ return defaultValue;
}
/**
@@ -715,20 +739,21 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect(BluetoothDevice device) {
- boolean result = false;
-
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- result = service.connect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return result;
+ return defaultValue;
}
/**
@@ -741,20 +766,21 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
- boolean result = false;
-
final IBluetoothHidDevice service = getService();
- if (service != null) {
- try {
- result = service.disconnect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
-
- return result;
+ return defaultValue;
}
/**
@@ -782,23 +808,24 @@
})
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
- log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
- try {
- final IBluetoothHidDevice service = getService();
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+ if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ final IBluetoothHidDevice service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index fb4cbb2..ecbeddf 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -16,26 +16,29 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
@@ -245,7 +248,7 @@
"BluetoothHidHost", IBluetoothHidHost.class.getName()) {
@Override
public IBluetoothHidHost getServiceInterface(IBinder service) {
- return IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothHidHost.Stub.asInterface(service);
}
};
@@ -293,16 +296,20 @@
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.connect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -335,16 +342,20 @@
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.disconnect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -359,17 +370,23 @@
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -383,18 +400,23 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -412,16 +434,20 @@
throw new IllegalArgumentException("device must not be null");
}
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -470,20 +496,22 @@
throw new IllegalArgumentException("device must not be null");
}
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -529,16 +557,20 @@
throw new IllegalArgumentException("device must not be null");
}
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
private boolean isEnabled() {
@@ -562,18 +594,20 @@
public boolean virtualUnplug(BluetoothDevice device) {
if (DBG) log("virtualUnplug(" + device + ")");
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.virtualUnplug(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.virtualUnplug(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
-
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
-
+ return defaultValue;
}
/**
@@ -589,16 +623,20 @@
public boolean getProtocolMode(BluetoothDevice device) {
if (VDBG) log("getProtocolMode(" + device + ")");
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getProtocolMode(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.getProtocolMode(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -614,16 +652,20 @@
public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
if (DBG) log("setProtocolMode(" + device + ")");
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.setProtocolMode(device, protocolMode, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setProtocolMode(device, protocolMode, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -646,17 +688,21 @@
+ "bufferSize=" + bufferSize);
}
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getReport(device, reportType, reportId, bufferSize,
- mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.getReport(device, reportType, reportId, bufferSize, mAttributionSource,
+ recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -674,16 +720,20 @@
public boolean setReport(BluetoothDevice device, byte reportType, String report) {
if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.setReport(device, reportType, report, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setReport(device, reportType, report, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -700,16 +750,20 @@
public boolean sendData(BluetoothDevice device, String report) {
if (DBG) log("sendData(" + device + "), report=" + report);
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.sendData(device, report, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.sendData(device, report, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -725,16 +779,20 @@
public boolean getIdleTime(BluetoothDevice device) {
if (DBG) log("getIdletime(" + device + ")");
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getIdleTime(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.getIdleTime(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -751,16 +809,20 @@
public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
final IBluetoothHidHost service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.setIdleTime(device, idleTime, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setIdleTime(device, idleTime, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java
index d7940eb..15db686 100644
--- a/core/java/android/bluetooth/BluetoothLeAudio.java
+++ b/core/java/android/bluetooth/BluetoothLeAudio.java
@@ -17,26 +17,28 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.SuppressLint;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.CloseGuard;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the public APIs to control the LeAudio profile.
@@ -333,7 +335,7 @@
IBluetoothLeAudio.class.getName()) {
@Override
public IBluetoothLeAudio getServiceInterface(IBinder service) {
- return IBluetoothLeAudio.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothLeAudio.Stub.asInterface(service);
}
};
@@ -387,17 +389,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect(@Nullable BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled() && isValidDevice(device)) {
- return service.connect(device, mAttributionSource);
+ final IBluetoothLeAudio service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -427,17 +433,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(@Nullable BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled() && isValidDevice(device)) {
- return service.disconnect(device, mAttributionSource);
+ final IBluetoothLeAudio service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -448,18 +458,24 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()) {
+ final IBluetoothLeAudio service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
}
+ return defaultValue;
}
/**
@@ -471,19 +487,24 @@
public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
@NonNull int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()) {
+ final IBluetoothLeAudio service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
}
+ return defaultValue;
}
/**
@@ -495,18 +516,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()
- && isValidDevice(device)) {
- return service.getConnectionState(device, mAttributionSource);
+ final IBluetoothLeAudio service = getService();
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
}
+ return defaultValue;
}
/**
@@ -533,19 +557,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) log("setActiveDevice(" + device + ")");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()
- && ((device == null) || isValidDevice(device))) {
- service.setActiveDevice(device, mAttributionSource);
- return true;
+ final IBluetoothLeAudio service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled() && ((device == null) || isValidDevice(device))) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setActiveDevice(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -559,19 +585,25 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getActiveDevices() {
- if (VDBG) log("getActiveDevices()");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()) {
+ if (VDBG) log("getActiveDevice()");
+ final IBluetoothLeAudio service = getService();
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getActiveDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getActiveDevices(mAttributionSource), mAttributionSource);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<>();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<>();
}
+ return defaultValue;
}
/**
@@ -585,17 +617,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getGroupId(@NonNull BluetoothDevice device) {
if (VDBG) log("getGroupId()");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()) {
- return service.getGroupId(device, mAttributionSource);
+ final IBluetoothLeAudio service = getService();
+ final int defaultValue = GROUP_ID_INVALID;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getGroupId(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return GROUP_ID_INVALID;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return GROUP_ID_INVALID;
}
+ return defaultValue;
}
/**
@@ -608,17 +644,18 @@
@RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED})
public void setVolume(int volume) {
if (VDBG) log("setVolume(vol: " + volume + " )");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()) {
- service.setVolume(volume, mAttributionSource);
- return;
+ final IBluetoothLeAudio service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled()) {
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.setVolume(volume, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return;
}
}
@@ -637,16 +674,20 @@
public boolean groupAddNode(int group_id, @NonNull BluetoothDevice device) {
if (VDBG) log("groupAddNode()");
final IBluetoothLeAudio service = getService();
- try {
- if (service != null && mAdapter.isEnabled()) {
- return service.groupAddNode(group_id, device, mAttributionSource);
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.groupAddNode(group_id, device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -665,16 +706,20 @@
public boolean groupRemoveNode(int group_id, @NonNull BluetoothDevice device) {
if (VDBG) log("groupRemoveNode()");
final IBluetoothLeAudio service = getService();
- try {
- if (service != null && mAdapter.isEnabled()) {
- return service.groupRemoveNode(group_id, device, mAttributionSource);
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.groupRemoveNode(group_id, device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -697,22 +742,23 @@
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()
- && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+ final IBluetoothLeAudio service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -730,18 +776,21 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
- try {
- final IBluetoothLeAudio service = getService();
- if (service != null && mAdapter.isEnabled()
- && isValidDevice(device)) {
- return service.getConnectionPolicy(device, mAttributionSource);
+ final IBluetoothLeAudio service = getService();
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
+ return defaultValue;
}
diff --git a/core/java/android/bluetooth/BluetoothLeCall.java b/core/java/android/bluetooth/BluetoothLeCall.java
new file mode 100644
index 0000000..fb7789d
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeCall.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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 android.bluetooth;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Representation of Call
+ *
+ * @hide
+ */
+public final class BluetoothLeCall implements Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = "STATE_", value = {
+ STATE_INCOMING,
+ STATE_DIALING,
+ STATE_ALERTING,
+ STATE_ACTIVE,
+ STATE_LOCALLY_HELD,
+ STATE_REMOTELY_HELD,
+ STATE_LOCALLY_AND_REMOTELY_HELD
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {
+ }
+
+ /**
+ * A remote party is calling (incoming call).
+ *
+ * @hide
+ */
+ public static final int STATE_INCOMING = 0x00;
+
+ /**
+ * The process to call the remote party has started but the remote party is not
+ * being alerted (outgoing call).
+ *
+ * @hide
+ */
+ public static final int STATE_DIALING = 0x01;
+
+ /**
+ * A remote party is being alerted (outgoing call).
+ *
+ * @hide
+ */
+ public static final int STATE_ALERTING = 0x02;
+
+ /**
+ * The call is in an active conversation.
+ *
+ * @hide
+ */
+ public static final int STATE_ACTIVE = 0x03;
+
+ /**
+ * The call is connected but held locally. “Locally Held” implies that either
+ * the server or the client can affect the state.
+ *
+ * @hide
+ */
+ public static final int STATE_LOCALLY_HELD = 0x04;
+
+ /**
+ * The call is connected but held remotely. “Remotely Held” means that the state
+ * is controlled by the remote party of a call.
+ *
+ * @hide
+ */
+ public static final int STATE_REMOTELY_HELD = 0x05;
+
+ /**
+ * The call is connected but held both locally and remotely.
+ *
+ * @hide
+ */
+ public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06;
+
+ /**
+ * Whether the call direction is outgoing.
+ *
+ * @hide
+ */
+ public static final int FLAG_OUTGOING_CALL = 0x00000001;
+
+ /**
+ * Whether the call URI and Friendly Name are withheld by server.
+ *
+ * @hide
+ */
+ public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002;
+
+ /**
+ * Whether the call URI and Friendly Name are withheld by network.
+ *
+ * @hide
+ */
+ public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004;
+
+ /** Unique UUID that identifies this call */
+ private UUID mUuid;
+
+ /** Remote Caller URI */
+ private String mUri;
+
+ /** Caller friendly name */
+ private String mFriendlyName;
+
+ /** Call state */
+ private @State int mState;
+
+ /** Call flags */
+ private int mCallFlags;
+
+ /** @hide */
+ public BluetoothLeCall(@NonNull BluetoothLeCall that) {
+ mUuid = new UUID(that.getUuid().getMostSignificantBits(),
+ that.getUuid().getLeastSignificantBits());
+ mUri = that.mUri;
+ mFriendlyName = that.mFriendlyName;
+ mState = that.mState;
+ mCallFlags = that.mCallFlags;
+ }
+
+ /** @hide */
+ public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName,
+ @State int state, int callFlags) {
+ mUuid = uuid;
+ mUri = uri;
+ mFriendlyName = friendlyName;
+ mState = state;
+ mCallFlags = callFlags;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ BluetoothLeCall that = (BluetoothLeCall) o;
+ return mUuid.equals(that.mUuid) && mUri.equals(that.mUri)
+ && mFriendlyName.equals(that.mFriendlyName) && mState == that.mState
+ && mCallFlags == that.mCallFlags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags);
+ }
+
+ /**
+ * Returns a string representation of this BluetoothLeCall.
+ *
+ * <p>
+ * Currently this is the UUID.
+ *
+ * @return string representation of this BluetoothLeCall
+ */
+ @Override
+ public String toString() {
+ return mUuid.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeParcelable(new ParcelUuid(mUuid), 0);
+ out.writeString(mUri);
+ out.writeString(mFriendlyName);
+ out.writeInt(mState);
+ out.writeInt(mCallFlags);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR =
+ new Parcelable.Creator<BluetoothLeCall>() {
+ public BluetoothLeCall createFromParcel(Parcel in) {
+ return new BluetoothLeCall(in);
+ }
+
+ public BluetoothLeCall[] newArray(int size) {
+ return new BluetoothLeCall[size];
+ }
+ };
+
+ private BluetoothLeCall(Parcel in) {
+ mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+ mUri = in.readString();
+ mFriendlyName = in.readString();
+ mState = in.readInt();
+ mCallFlags = in.readInt();
+ }
+
+ /**
+ * Returns an UUID of this BluetoothLeCall.
+ *
+ * <p>
+ * An UUID is unique identifier of a BluetoothLeCall.
+ *
+ * @return UUID of this BluetoothLeCall
+ * @hide
+ */
+ public @NonNull UUID getUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Returns a URI of the remote party of this BluetoothLeCall.
+ *
+ * @return string representation of this BluetoothLeCall
+ * @hide
+ */
+ public @NonNull String getUri() {
+ return mUri;
+ }
+
+ /**
+ * Returns a friendly name of the call.
+ *
+ * @return friendly name representation of this BluetoothLeCall
+ * @hide
+ */
+ public @NonNull String getFriendlyName() {
+ return mFriendlyName;
+ }
+
+ /**
+ * Returns the call state.
+ *
+ * @return the state of this BluetoothLeCall
+ * @hide
+ */
+ public @State int getState() {
+ return mState;
+ }
+
+ /**
+ * Returns the call flags.
+ *
+ * @return call flags
+ * @hide
+ */
+ public int getCallFlags() {
+ return mCallFlags;
+ }
+
+ /**
+ * Whether the call direction is incoming.
+ *
+ * @return true if incoming call, false otherwise
+ * @hide
+ */
+ public boolean isIncomingCall() {
+ return (mCallFlags & FLAG_OUTGOING_CALL) == 0;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothLeCallControl.java b/core/java/android/bluetooth/BluetoothLeCallControl.java
new file mode 100644
index 0000000..5283e08
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeCallControl.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright 2019 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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 android.bluetooth;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+import android.annotation.SuppressLint;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides the APIs to control the Call Control profile.
+ *
+ * <p>
+ * This class provides Bluetooth Telephone Bearer Service functionality,
+ * allowing applications to expose a GATT Service based interface to control the
+ * state of the calls by remote devices such as LE audio devices.
+ *
+ * <p>
+ * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothLeCallControl proxy object.
+ *
+ * @hide
+ */
+public final class BluetoothLeCallControl implements BluetoothProfile {
+ private static final String TAG = "BluetoothLeCallControl";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ /** @hide */
+ @IntDef(prefix = "RESULT_", value = {
+ RESULT_SUCCESS,
+ RESULT_ERROR_UNKNOWN_CALL_ID,
+ RESULT_ERROR_INVALID_URI,
+ RESULT_ERROR_APPLICATION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Result {
+ }
+
+ /**
+ * Opcode write was successful.
+ *
+ * @hide
+ */
+ public static final int RESULT_SUCCESS = 0;
+
+ /**
+ * Unknown call Id has been used in the operation.
+ *
+ * @hide
+ */
+ public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1;
+
+ /**
+ * The URI provided in {@link Callback#onPlaceCallRequest} is invalid.
+ *
+ * @hide
+ */
+ public static final int RESULT_ERROR_INVALID_URI = 2;
+
+ /**
+ * Application internal error.
+ *
+ * @hide
+ */
+ public static final int RESULT_ERROR_APPLICATION = 3;
+
+ /** @hide */
+ @IntDef(prefix = "TERMINATION_REASON_", value = {
+ TERMINATION_REASON_INVALID_URI,
+ TERMINATION_REASON_FAIL,
+ TERMINATION_REASON_REMOTE_HANGUP,
+ TERMINATION_REASON_SERVER_HANGUP,
+ TERMINATION_REASON_LINE_BUSY,
+ TERMINATION_REASON_NETWORK_CONGESTION,
+ TERMINATION_REASON_CLIENT_HANGUP,
+ TERMINATION_REASON_NO_SERVICE,
+ TERMINATION_REASON_NO_ANSWER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TerminationReason {
+ }
+
+ /**
+ * Remote Caller ID value used to place a call was formed improperly.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_INVALID_URI = 0x00;
+
+ /**
+ * Call fail.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_FAIL = 0x01;
+
+ /**
+ * Remote party ended call.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02;
+
+ /**
+ * Call ended from the server.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03;
+
+ /**
+ * Line busy.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_LINE_BUSY = 0x04;
+
+ /**
+ * Network congestion.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05;
+
+ /**
+ * Client terminated.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06;
+
+ /**
+ * No service.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_NO_SERVICE = 0x07;
+
+ /**
+ * No answer.
+ *
+ * @hide
+ */
+ public static final int TERMINATION_REASON_NO_ANSWER = 0x08;
+
+ /*
+ * Flag indicating support for hold/unhold call feature.
+ *
+ * @hide
+ */
+ public static final int CAPABILITY_HOLD_CALL = 0x00000001;
+
+ /**
+ * Flag indicating support for joining calls feature.
+ *
+ * @hide
+ */
+ public static final int CAPABILITY_JOIN_CALLS = 0x00000002;
+
+ private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102;
+ private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103;
+
+ private static final int REG_TIMEOUT = 10000;
+
+ /**
+ * The template class is used to call callback functions on events from the TBS
+ * server. Callback functions are wrapped in this class and registered to the
+ * Android system during app registration.
+ *
+ * @hide
+ */
+ public abstract static class Callback {
+
+ private static final String TAG = "BluetoothLeCallControl.Callback";
+
+ /**
+ * Called when a remote client requested to accept the call.
+ *
+ * <p>
+ * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+ * request.
+ *
+ * @param requestId The Id of the request
+ * @param callId The call Id requested to be accepted
+ * @hide
+ */
+ public abstract void onAcceptCall(int requestId, @NonNull UUID callId);
+
+ /**
+ * A remote client has requested to terminate the call.
+ *
+ * <p>
+ * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+ * request.
+ *
+ * @param requestId The Id of the request
+ * @param callId The call Id requested to terminate
+ * @hide
+ */
+ public abstract void onTerminateCall(int requestId, @NonNull UUID callId);
+
+ /**
+ * A remote client has requested to hold the call.
+ *
+ * <p>
+ * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+ * request.
+ *
+ * @param requestId The Id of the request
+ * @param callId The call Id requested to be put on hold
+ * @hide
+ */
+ public void onHoldCall(int requestId, @NonNull UUID callId) {
+ Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
+ }
+
+ /**
+ * A remote client has requested to unhold the call.
+ *
+ * <p>
+ * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+ * request.
+ *
+ * @param requestId The Id of the request
+ * @param callId The call Id requested to unhold
+ * @hide
+ */
+ public void onUnholdCall(int requestId, @NonNull UUID callId) {
+ Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
+ }
+
+ /**
+ * A remote client has requested to place a call.
+ *
+ * <p>
+ * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+ * request.
+ *
+ * @param requestId The Id of the request
+ * @param callId The Id to be assigned for the new call
+ * @param uri The caller URI requested
+ * @hide
+ */
+ public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri);
+
+ /**
+ * A remote client has requested to join the calls.
+ *
+ * <p>
+ * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+ * request.
+ *
+ * @param requestId The Id of the request
+ * @param callIds The call Id list requested to join
+ * @hide
+ */
+ public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) {
+ Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!");
+ }
+ }
+
+ private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub {
+
+ private final Executor mExecutor;
+ private final Callback mCallback;
+
+ CallbackWrapper(Executor executor, Callback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onBearerRegistered(int ccid) {
+ synchronized (mServerIfLock) {
+ if (mCallback != null) {
+ mCcid = ccid;
+ mServerIfLock.notifyAll();
+ } else {
+ // registration timeout
+ Log.e(TAG, "onBearerRegistered: mCallback is null");
+ }
+ }
+ }
+
+ @Override
+ public void onAcceptCall(int requestId, ParcelUuid uuid) {
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid()));
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public void onTerminateCall(int requestId, ParcelUuid uuid) {
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid()));
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public void onHoldCall(int requestId, ParcelUuid uuid) {
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid()));
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public void onUnholdCall(int requestId, ParcelUuid uuid) {
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid()));
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) {
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri));
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) {
+ List<UUID> uuids = new ArrayList<>();
+ for (ParcelUuid parcelUuid : parcelUuids) {
+ uuids.add(parcelUuid.getUuid());
+ }
+
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids));
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+ };
+
+ private Context mContext;
+ private ServiceListener mServiceListener;
+ private volatile IBluetoothLeCallControl mService;
+ private BluetoothAdapter mAdapter;
+ private int mCcid = 0;
+ private String mToken;
+ private Callback mCallback = null;
+ private Object mServerIfLock = new Object();
+
+ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG)
+ Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ doUnbind();
+ } else {
+ doBind();
+ }
+ }
+ };
+
+ /**
+ * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth
+ * telephone bearer service.
+ */
+ /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) {
+ mContext = context;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mServiceListener = listener;
+
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+
+ doBind();
+ }
+
+ private boolean doBind() {
+ synchronized (mConnection) {
+ if (mService == null) {
+ if (VDBG)
+ Log.d(TAG, "Binding service...");
+ try {
+ return mAdapter.getBluetoothManager().
+ bindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL,
+ mConnection);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to bind TelephoneBearerService", e);
+ }
+ }
+ }
+ return false;
+ }
+
+ private void doUnbind() {
+ synchronized (mConnection) {
+ if (mService != null) {
+ if (VDBG)
+ Log.d(TAG, "Unbinding service...");
+ try {
+ mAdapter.getBluetoothManager().
+ unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL,
+ mConnection);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to unbind TelephoneBearerService", e);
+ } finally {
+ mService = null;
+ }
+ }
+ }
+ }
+
+ /* package */ void close() {
+ if (VDBG)
+ log("close()");
+ unregisterBearer();
+
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "", re);
+ }
+ }
+ mServiceListener = null;
+ doUnbind();
+ }
+
+ private IBluetoothLeCallControl getService() {
+ return mService;
+ }
+
+ /**
+ * Not supported
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public int getConnectionState(@Nullable BluetoothDevice device) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ /**
+ * Not supported
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public @NonNull List<BluetoothDevice> getConnectedDevices() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ /**
+ * Not supported
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
+ @NonNull int[] states) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ /**
+ * Register Telephone Bearer exposing the interface that allows remote devices
+ * to track and control the call states.
+ *
+ * <p>
+ * This is an asynchronous call. The callback is used to notify success or
+ * failure if the function returns true.
+ *
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * <!-- The UCI is a String identifier of the telephone bearer as defined at
+ * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers
+ * (login required). -->
+ *
+ * <!-- The examples of common URI schemes can be found in
+ * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml -->
+ *
+ * <!-- The Technology is an integer value. The possible values are defined at
+ * https://www.bluetooth.com/specifications/assigned-numbers (login required).
+ * -->
+ *
+ * @param uci Bearer Unique Client Identifier
+ * @param uriSchemes URI Schemes supported list
+ * @param capabilities bearer capabilities
+ * @param provider Network provider name
+ * @param technology Network technology
+ * @param executor {@link Executor} object on which callback will be
+ * executed. The Executor object is required.
+ * @param callback {@link Callback} object to which callback messages will
+ * be sent. The Callback object is required.
+ * @return true on success, false otherwise
+ * @hide
+ */
+ @SuppressLint("ExecutorRegistration")
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean registerBearer(@Nullable String uci,
+ @NonNull List<String> uriSchemes, int capabilities,
+ @NonNull String provider, int technology,
+ @NonNull Executor executor, @NonNull Callback callback) {
+ if (DBG) {
+ Log.d(TAG, "registerBearer");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("null parameter: " + callback);
+ }
+ if (mCcid != 0) {
+ return false;
+ }
+
+ mToken = uci;
+
+ final IBluetoothLeCallControl service = getService();
+ if (service != null) {
+ synchronized (mServerIfLock) {
+ if (mCallback != null) {
+ Log.e(TAG, "Bearer can be opened only once");
+ return false;
+ }
+
+ mCallback = callback;
+ try {
+ CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
+ service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities,
+ provider, technology);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ mCallback = null;
+ return false;
+ }
+
+ try {
+ mServerIfLock.wait(REG_TIMEOUT);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "" + e);
+ mCallback = null;
+ }
+
+ if (mCcid == 0) {
+ mCallback = null;
+ return false;
+ }
+
+ return true;
+ }
+ }
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+
+ return false;
+ }
+
+ /**
+ * Unregister Telephone Bearer Service and destroy all the associated data.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void unregisterBearer() {
+ if (DBG) {
+ Log.d(TAG, "unregisterBearer");
+ }
+ if (mCcid == 0) {
+ return;
+ }
+
+ int ccid = mCcid;
+ mCcid = 0;
+ mCallback = null;
+
+ final IBluetoothLeCallControl service = getService();
+ if (service != null) {
+ try {
+ service.unregisterBearer(mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ }
+
+ /**
+ * Get the Content Control ID (CCID) value.
+ *
+ * @return ccid Content Control ID value
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public int getContentControlId() {
+ return mCcid;
+ }
+
+ /**
+ * Notify about the newly added call.
+ *
+ * <p>
+ * This shall be called as early as possible after the call has been added.
+ *
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param call Newly added call
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void onCallAdded(@NonNull BluetoothLeCall call) {
+ if (DBG) {
+ Log.d(TAG, "onCallAdded: call=" + call);
+ }
+ if (mCcid == 0) {
+ return;
+ }
+
+ final IBluetoothLeCallControl service = getService();
+ if (service != null) {
+ try {
+ service.callAdded(mCcid, call);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ }
+
+ /**
+ * Notify about the removed call.
+ *
+ * <p>
+ * This shall be called as early as possible after the call has been removed.
+ *
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param callId The Id of a call that has been removed
+ * @param reason Call termination reason
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) {
+ if (DBG) {
+ Log.d(TAG, "callRemoved: callId=" + callId);
+ }
+ if (mCcid == 0) {
+ return;
+ }
+
+ final IBluetoothLeCallControl service = getService();
+ if (service != null) {
+ try {
+ service.callRemoved(mCcid, new ParcelUuid(callId), reason);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ }
+
+ /**
+ * Notify the call state change
+ *
+ * <p>
+ * This shall be called as early as possible after the state of the call has
+ * changed.
+ *
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param callId The call Id that state has been changed
+ * @param state Call state
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) {
+ if (DBG) {
+ Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state);
+ }
+ if (mCcid == 0) {
+ return;
+ }
+
+ final IBluetoothLeCallControl service = getService();
+ if (service != null) {
+ try {
+ service.callStateChanged(mCcid, new ParcelUuid(callId), state);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ }
+
+ /**
+ * Provide the current calls list
+ *
+ * <p>
+ * This function must be invoked after registration if application has any
+ * calls.
+ *
+ * @param calls current calls list
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void currentCallsList(@NonNull List<BluetoothLeCall> calls) {
+ final IBluetoothLeCallControl service = getService();
+ if (service != null) {
+ try {
+ service.currentCallsList(mCcid, calls);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ }
+
+ /**
+ * Provide the network current status
+ *
+ * <p>
+ * This function must be invoked on change of network state.
+ *
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * <!-- The Technology is an integer value. The possible values are defined at
+ * https://www.bluetooth.com/specifications/assigned-numbers (login required).
+ * -->
+ *
+ * @param provider Network provider name
+ * @param technology Network technology
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void networkStateChanged(@NonNull String provider, int technology) {
+ if (DBG) {
+ Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology);
+ }
+ if (mCcid == 0) {
+ return;
+ }
+
+ final IBluetoothLeCallControl service = getService();
+ if (service != null) {
+ try {
+ service.networkStateChanged(mCcid, provider, technology);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ }
+
+ /**
+ * Send a response to a call control request to a remote device.
+ *
+ * <p>
+ * This function must be invoked in when a request is received by one of these
+ * callback methods:
+ *
+ * <ul>
+ * <li>{@link Callback#onAcceptCall}
+ * <li>{@link Callback#onTerminateCall}
+ * <li>{@link Callback#onHoldCall}
+ * <li>{@link Callback#onUnholdCall}
+ * <li>{@link Callback#onPlaceCall}
+ * <li>{@link Callback#onJoinCalls}
+ * </ul>
+ *
+ * @param requestId The ID of the request that was received with the callback
+ * @param result The result of the request to be sent to the remote devices
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void requestResult(int requestId, @Result int result) {
+ if (DBG) {
+ Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result);
+ }
+ if (mCcid == 0) {
+ return;
+ }
+
+ final IBluetoothLeCallControl service = getService();
+ if (service != null) {
+ try {
+ service.requestResult(mCcid, requestId, result);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ }
+
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ private static boolean isValidDevice(@Nullable BluetoothDevice device) {
+ return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+ }
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+
+ private final IBluetoothProfileServiceConnection mConnection =
+ new IBluetoothProfileServiceConnection.Stub() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) {
+ Log.d(TAG, "Proxy object connected");
+ }
+ mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service));
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) {
+ Log.d(TAG, "Proxy object disconnected");
+ }
+ doUnbind();
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED));
+ }
+ };
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_TBS_SERVICE_CONNECTED: {
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL,
+ BluetoothLeCallControl.this);
+ }
+ break;
+ }
+ case MESSAGE_TBS_SERVICE_DISCONNECTED: {
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL);
+ }
+ break;
+ }
+ }
+ }
+ };
+}
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index 20152f3..fef6f22 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -16,17 +16,12 @@
package android.bluetooth;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresNoPermission;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
-import android.app.ActivityThread;
-import android.app.AppGlobals;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -69,37 +64,11 @@
* @hide
*/
public BluetoothManager(Context context) {
- mAttributionSource = resolveAttributionSource(context);
+ mAttributionSource = (context != null) ? context.getAttributionSource() :
+ AttributionSource.myAttributionSource();
mAdapter = BluetoothAdapter.createAdapter(mAttributionSource);
}
- /** {@hide} */
- public static @NonNull AttributionSource resolveAttributionSource(@Nullable Context context) {
- AttributionSource res = null;
- if (context != null) {
- res = context.getAttributionSource();
- }
- if (res == null) {
- res = ActivityThread.currentAttributionSource();
- }
- if (res == null) {
- int uid = android.os.Process.myUid();
- if (uid == android.os.Process.ROOT_UID) {
- uid = android.os.Process.SYSTEM_UID;
- }
- try {
- res = new AttributionSource.Builder(uid)
- .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
- .build();
- } catch (RemoteException ignored) {
- }
- }
- if (res == null) {
- throw new IllegalStateException("Failed to resolve AttributionSource");
- }
- return res;
- }
-
/**
* Get the BLUETOOTH Adapter for this device.
*
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 8679651..56e4972 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -16,28 +16,31 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresNoPermission;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.SdkConstant.SdkConstantType;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.CloseGuard;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the APIs to control the Bluetooth MAP
@@ -88,7 +91,7 @@
"BluetoothMap", IBluetoothMap.class.getName()) {
@Override
public IBluetoothMap getServiceInterface(IBinder service) {
- return IBluetoothMap.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothMap.Stub.asInterface(service);
}
};
@@ -143,17 +146,20 @@
public int getState() {
if (VDBG) log("getState()");
final IBluetoothMap service = getService();
- if (service != null) {
- try {
- return service.getState(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final int defaultValue = BluetoothMap.STATE_ERROR;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getState(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return BluetoothMap.STATE_ERROR;
+ return defaultValue;
}
/**
@@ -169,18 +175,23 @@
public BluetoothDevice getClient() {
if (VDBG) log("getClient()");
final IBluetoothMap service = getService();
- if (service != null) {
- try {
- return Attributable.setAttributionSource(
- service.getClient(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final BluetoothDevice defaultValue = null;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<BluetoothDevice> recv =
+ new SynchronousResultReceiver();
+ service.getClient(mAttributionSource, recv);
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return null;
+ return defaultValue;
}
/**
@@ -195,17 +206,20 @@
public boolean isConnected(BluetoothDevice device) {
if (VDBG) log("isConnected(" + device + ")");
final IBluetoothMap service = getService();
- if (service != null) {
- try {
- return service.isConnected(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isConnected(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -234,16 +248,20 @@
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothMap service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.disconnect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -285,17 +303,23 @@
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (DBG) log("getConnectedDevices()");
final IBluetoothMap service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -310,18 +334,23 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) log("getDevicesMatchingStates()");
final IBluetoothMap service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -336,16 +365,21 @@
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getConnectionState(" + device + ")");
final IBluetoothMap service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv =
+ new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -391,20 +425,22 @@
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothMap service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -447,16 +483,20 @@
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothMap service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 042b586..03536f9a 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,19 +28,20 @@
import android.app.PendingIntent;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
import android.net.Uri;
-import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the APIs to control the Bluetooth MAP MCE Profile.
@@ -181,7 +184,7 @@
"BluetoothMapClient", IBluetoothMapClient.class.getName()) {
@Override
public IBluetoothMapClient getServiceInterface(IBinder service) {
- return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothMapClient.Stub.asInterface(service);
}
};
@@ -222,17 +225,20 @@
public boolean isConnected(BluetoothDevice device) {
if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
final IBluetoothMapClient service = getService();
- if (service != null) {
- try {
- return service.isConnected(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isConnected(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -249,17 +255,20 @@
public boolean connect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
final IBluetoothMapClient service = getService();
- if (service != null) {
- try {
- return service.connect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -278,15 +287,20 @@
public boolean disconnect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "disconnect(" + device + ")");
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.disconnect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -301,17 +315,23 @@
public List<BluetoothDevice> getConnectedDevices() {
if (DBG) Log.d(TAG, "getConnectedDevices()");
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<>();
+ return defaultValue;
}
/**
@@ -326,18 +346,23 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<>();
+ return defaultValue;
}
/**
@@ -352,16 +377,20 @@
public int getConnectionState(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver<>();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -406,20 +435,22 @@
@ConnectionPolicy int connectionPolicy) {
if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -461,16 +492,20 @@
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
/**
@@ -495,18 +530,8 @@
public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts,
@NonNull String message, @Nullable PendingIntent sentIntent,
@Nullable PendingIntent deliveredIntent) {
- if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
- final IBluetoothMapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- return service.sendMessage(device, contacts.toArray(new Uri[contacts.size()]),
- message, sentIntent, deliveredIntent, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
- }
- }
- return false;
+ return sendMessage(device, contacts.toArray(new Uri[contacts.size()]), message, sentIntent,
+ deliveredIntent);
}
/**
@@ -532,16 +557,21 @@
PendingIntent sentIntent, PendingIntent deliveredIntent) {
if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent,
- mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.sendMessage(device, contacts, message, sentIntent, deliveredIntent,
+ mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- return false;
+ return defaultValue;
}
/**
@@ -559,15 +589,20 @@
public boolean getUnreadMessages(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getUnreadMessages(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.getUnreadMessages(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- return false;
+ return defaultValue;
}
/**
@@ -581,13 +616,21 @@
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isUploadingSupported(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "isUploadingSupported(" + device + ")");
final IBluetoothMapClient service = getService();
- try {
- return (service != null && isEnabled() && isValidDevice(device))
- && ((service.getSupportedFeatures(device, mAttributionSource)
- & UPLOADING_FEATURE_BITMASK) > 0);
- } catch (RemoteException e) {
- Log.e(TAG, e.getMessage());
+ final int defaultValue = 0;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getSupportedFeatures(device, mAttributionSource, recv);
+ return (recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue)
+ & UPLOADING_FEATURE_BITMASK) > 0;
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
return false;
}
@@ -616,16 +659,21 @@
public boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")");
final IBluetoothMapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device) && handle != null &&
- (status == READ || status == UNREAD || status == UNDELETED || status == DELETED)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device) && handle != null && (status == READ
+ || status == UNREAD || status == UNDELETED || status == DELETED)) {
try {
- return service.setMessageStatus(device, handle, status, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setMessageStatus(device, handle, status, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- return false;
+ return defaultValue;
}
private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 577be3d..d4ad4ef4 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -16,7 +16,8 @@
package android.bluetooth;
-import android.Manifest;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -27,19 +28,20 @@
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the APIs to control the Bluetooth Pan
@@ -190,7 +192,7 @@
"BluetoothPan", IBluetoothPan.class.getName()) {
@Override
public IBluetoothPan getServiceInterface(IBinder service) {
- return IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothPan.Stub.asInterface(service);
}
};
@@ -251,16 +253,20 @@
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothPan service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.connect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -291,16 +297,20 @@
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothPan service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.disconnect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -324,22 +334,23 @@
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
- try {
- final IBluetoothPan service = getService();
- if (service != null && isEnabled()
- && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+ final IBluetoothPan service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return defaultValue;
}
/**
@@ -356,17 +367,23 @@
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothPan service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -383,18 +400,23 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothPan service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -411,16 +433,20 @@
public int getConnectionState(@NonNull BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
final IBluetoothPan service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -440,11 +466,16 @@
String pkgName = mContext.getOpPackageName();
if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
final IBluetoothPan service = getService();
- if (service != null && isEnabled()) {
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
- service.setBluetoothTethering(value, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.setBluetoothTethering(value, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
}
@@ -461,14 +492,20 @@
public boolean isTetheringOn() {
if (VDBG) log("isTetheringOn()");
final IBluetoothPan service = getService();
- if (service != null && isEnabled()) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
- return service.isTetheringOn(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isTetheringOn(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- return false;
+ return defaultValue;
}
@UnsupportedAppUsage
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 8ee38d3..de2db9c 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -24,17 +24,11 @@
import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Log;
import java.util.ArrayList;
@@ -97,10 +91,6 @@
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
- private volatile IBluetoothPbap mService;
- private final Context mContext;
- private ServiceListener mServiceListener;
- private final BluetoothAdapter mAdapter;
private final AttributionSource mAttributionSource;
/** @hide */
@@ -114,87 +104,25 @@
*/
public static final int RESULT_CANCELED = 2;
- @SuppressLint("AndroidFrameworkBluetoothPermission")
- private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
- new IBluetoothStateChangeCallback.Stub() {
- public void onBluetoothStateChange(boolean up) {
- log("onBluetoothStateChange: up=" + up);
- if (!up) {
- doUnbind();
- } else {
- doBind();
- }
+ private BluetoothAdapter mAdapter;
+ private final BluetoothProfileConnector<IBluetoothPbap> mProfileConnector =
+ new BluetoothProfileConnector(this, BluetoothProfile.PBAP, "BluetoothPbap",
+ IBluetoothPbap.class.getName()) {
+ @Override
+ public IBluetoothPbap getServiceInterface(IBinder service) {
+ return IBluetoothPbap.Stub.asInterface(service);
}
- };
+ };
/**
* Create a BluetoothPbap proxy object.
*
* @hide
*/
- public BluetoothPbap(Context context, ServiceListener l, BluetoothAdapter adapter) {
- mContext = context;
- mServiceListener = l;
+ public BluetoothPbap(Context context, ServiceListener listener, BluetoothAdapter adapter) {
mAdapter = adapter;
mAttributionSource = adapter.getAttributionSource();
-
- // Preserve legacy compatibility where apps were depending on
- // registerStateChangeCallback() performing a permissions check which
- // has been relaxed in modern platform versions
- if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R
- && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Need BLUETOOTH permission");
- }
-
- IBluetoothManager mgr = mAdapter.getBluetoothManager();
- if (mgr != null) {
- try {
- mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
- } catch (RemoteException re) {
- Log.e(TAG, "", re);
- }
- }
- doBind();
- }
-
- @SuppressLint("AndroidFrameworkRequiresPermission")
- boolean doBind() {
- synchronized (mConnection) {
- try {
- if (mService == null) {
- log("Binding service...");
- Intent intent = new Intent(IBluetoothPbap.class.getName());
- ComponentName comp = intent.resolveSystemService(
- mContext.getPackageManager(), 0);
- intent.setComponent(comp);
- if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
- UserHandle.CURRENT)) {
- Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
- return false;
- }
- }
- } catch (SecurityException se) {
- Log.e(TAG, "", se);
- return false;
- }
- }
- return true;
- }
-
- private void doUnbind() {
- synchronized (mConnection) {
- if (mService != null) {
- log("Unbinding service...");
- try {
- mContext.unbindService(mConnection);
- } catch (IllegalArgumentException ie) {
- Log.e(TAG, "", ie);
- } finally {
- mService = null;
- }
- }
- }
+ mProfileConnector.connect(context, listener);
}
/** @hide */
@@ -215,16 +143,11 @@
* @hide
*/
public synchronized void close() {
- IBluetoothManager mgr = mAdapter.getBluetoothManager();
- if (mgr != null) {
- try {
- mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
- } catch (RemoteException re) {
- Log.e(TAG, "", re);
- }
- }
- doUnbind();
- mServiceListener = null;
+ mProfileConnector.disconnect();
+ }
+
+ private IBluetoothPbap getService() {
+ return (IBluetoothPbap) mProfileConnector.getService();
}
/**
@@ -237,7 +160,7 @@
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
log("getConnectedDevices()");
- final IBluetoothPbap service = mService;
+ final IBluetoothPbap service = getService();
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
return new ArrayList<BluetoothDevice>();
@@ -266,7 +189,7 @@
public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
log("getConnectionState: device=" + device);
try {
- final IBluetoothPbap service = mService;
+ final IBluetoothPbap service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
return service.getConnectionState(device, mAttributionSource);
}
@@ -290,7 +213,7 @@
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states));
- final IBluetoothPbap service = mService;
+ final IBluetoothPbap service = getService();
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
return new ArrayList<BluetoothDevice>();
@@ -331,7 +254,7 @@
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
try {
- final IBluetoothPbap service = mService;
+ final IBluetoothPbap service = getService();
if (service != null && isEnabled()
&& isValidDevice(device)) {
if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
@@ -360,7 +283,7 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
log("disconnect()");
- final IBluetoothPbap service = mService;
+ final IBluetoothPbap service = getService();
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
return false;
@@ -374,25 +297,6 @@
return false;
}
- @SuppressLint("AndroidFrameworkBluetoothPermission")
- private final ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- log("Proxy object connected");
- mService = IBluetoothPbap.Stub.asInterface(service);
- if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.PBAP, BluetoothPbap.this);
- }
- }
-
- public void onServiceDisconnected(ComponentName className) {
- log("Proxy object disconnected");
- doUnbind();
- if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP);
- }
- }
- };
-
private boolean isEnabled() {
if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
return false;
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index c7dd6bd..e096de8 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -16,23 +16,25 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
import android.annotation.SdkConstant.SdkConstantType;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the APIs to control the Bluetooth PBAP Client Profile.
@@ -66,7 +68,7 @@
"BluetoothPbapClient", IBluetoothPbapClient.class.getName()) {
@Override
public IBluetoothPbapClient getServiceInterface(IBinder service) {
- return IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothPbapClient.Stub.asInterface(service);
}
};
@@ -125,18 +127,20 @@
log("connect(" + device + ") for PBAP Client.");
}
final IBluetoothPbapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- return service.connect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
- }
- }
+ final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.connect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -157,19 +161,21 @@
log("disconnect(" + device + ")" + new Exception());
}
final IBluetoothPbapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- service.disconnect(device, mAttributionSource);
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
- }
- }
+ final boolean defaultValue = true;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ return true;
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -186,19 +192,23 @@
log("getConnectedDevices()");
}
final IBluetoothPbapClient service = getService();
- if (service != null && isEnabled()) {
- try {
- return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
- }
- }
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -214,20 +224,23 @@
log("getDevicesMatchingStates()");
}
final IBluetoothPbapClient service = getService();
- if (service != null && isEnabled()) {
- try {
- return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
- mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
- }
- }
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -243,18 +256,20 @@
log("getConnectionState(" + device + ")");
}
final IBluetoothPbapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- }
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
private static void log(String msg) {
@@ -313,22 +328,22 @@
log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
}
final IBluetoothPbapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
- try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
- }
- }
+ final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -372,17 +387,19 @@
log("getConnectionPolicy(" + device + ")");
}
final IBluetoothPbapClient service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
- }
- }
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index e047e5d..d0f74e9 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -240,12 +240,19 @@
int LE_AUDIO_BROADCAST = 26;
/**
+ * @hide
+ * Telephone Bearer Service from Call Control Profile
+ *
+ */
+ int LE_CALL_CONTROL = 27;
+
+ /**
* Max profile ID. This value should be updated whenever a new profile is added to match
* the largest value assigned to a profile.
*
* @hide
*/
- int MAX_PROFILE_ID = 26;
+ int MAX_PROFILE_ID = 27;
/**
* Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java
index ecd5e40..79373f1a 100644
--- a/core/java/android/bluetooth/BluetoothProfileConnector.java
+++ b/core/java/android/bluetooth/BluetoothProfileConnector.java
@@ -16,12 +16,16 @@
package android.bluetooth;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
@@ -29,6 +33,7 @@
import android.util.CloseGuard;
import android.util.Log;
+import java.util.List;
/**
* Connector for Bluetooth profile proxies to bind manager service and
* profile services
@@ -57,6 +62,29 @@
}
};
+ private @Nullable ComponentName resolveSystemService(@NonNull Intent intent,
+ @NonNull PackageManager pm, @PackageManager.ComponentInfoFlags int flags) {
+ List<ResolveInfo> results = pm.queryIntentServices(intent, flags);
+ if (results == null) {
+ return null;
+ }
+ ComponentName comp = null;
+ for (int i = 0; i < results.size(); i++) {
+ ResolveInfo ri = results.get(i);
+ if ((ri.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ continue;
+ }
+ ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
+ ri.serviceInfo.name);
+ if (comp != null) {
+ throw new IllegalStateException("Multiple system services handle " + intent
+ + ": " + comp + ", " + foundComp);
+ }
+ comp = foundComp;
+ }
+ return comp;
+ }
+
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
logDebug("Proxy object connected");
@@ -99,8 +127,8 @@
mCloseGuard.open("doUnbind");
try {
Intent intent = new Intent(mServiceName);
- ComponentName comp = intent.resolveSystemService(
- mContext.getPackageManager(), 0);
+ ComponentName comp = resolveSystemService(intent, mContext.getPackageManager(),
+ 0);
intent.setComponent(comp);
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
UserHandle.CURRENT)) {
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index fda19ed..808fa39 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.RequiresNoPermission;
import android.annotation.RequiresPermission;
@@ -24,17 +26,18 @@
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the APIs to control the Bluetooth SIM
@@ -105,7 +108,7 @@
"BluetoothSap", IBluetoothSap.class.getName()) {
@Override
public IBluetoothSap getServiceInterface(IBinder service) {
- return IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothSap.Stub.asInterface(service);
}
};
@@ -156,17 +159,20 @@
public int getState() {
if (VDBG) log("getState()");
final IBluetoothSap service = getService();
- if (service != null) {
- try {
- return service.getState(mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final int defaultValue = BluetoothSap.STATE_ERROR;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getState(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return BluetoothSap.STATE_ERROR;
+ return defaultValue;
}
/**
@@ -181,18 +187,23 @@
public BluetoothDevice getClient() {
if (VDBG) log("getClient()");
final IBluetoothSap service = getService();
- if (service != null) {
- try {
- return Attributable.setAttributionSource(
- service.getClient(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final BluetoothDevice defaultValue = null;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<BluetoothDevice> recv =
+ new SynchronousResultReceiver();
+ service.getClient(mAttributionSource, recv);
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return null;
+ return defaultValue;
}
/**
@@ -207,17 +218,20 @@
public boolean isConnected(BluetoothDevice device) {
if (VDBG) log("isConnected(" + device + ")");
final IBluetoothSap service = getService();
- if (service != null) {
- try {
- return service.isConnected(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
+ final boolean defaultValue = false;
+ if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isConnected(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
}
- return false;
+ return defaultValue;
}
/**
@@ -245,16 +259,20 @@
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothSap service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.disconnect(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.disconnect(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -268,17 +286,23 @@
public List<BluetoothDevice> getConnectedDevices() {
if (DBG) log("getConnectedDevices()");
final IBluetoothSap service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -292,18 +316,23 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) log("getDevicesMatchingStates()");
final IBluetoothSap service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -317,16 +346,20 @@
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getConnectionState(" + device + ")");
final IBluetoothSap service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -371,20 +404,22 @@
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothSap service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -426,16 +461,20 @@
public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothSap service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothUtils.java b/core/java/android/bluetooth/BluetoothUtils.java
new file mode 100644
index 0000000..8674692
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothUtils.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.bluetooth;
+
+import java.time.Duration;
+
+/**
+ * {@hide}
+ */
+public final class BluetoothUtils {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private BluetoothUtils() {}
+
+ /**
+ * Timeout value for synchronous binder call
+ */
+ private static final Duration SYNC_CALLS_TIMEOUT = Duration.ofSeconds(5);
+
+ /**
+ * @return timeout value for synchronous binder call
+ */
+ static Duration getSyncTimeout() {
+ return SYNC_CALLS_TIMEOUT;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 858819e..2a8ff51 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -189,7 +189,7 @@
@NonNull
@SystemApi
public static final ParcelUuid CAP =
- ParcelUuid.fromString("EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE");
+ ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB");
/** @hide */
@NonNull
@SystemApi
diff --git a/core/java/android/bluetooth/BluetoothVolumeControl.java b/core/java/android/bluetooth/BluetoothVolumeControl.java
index 678c11a..27532aa 100644
--- a/core/java/android/bluetooth/BluetoothVolumeControl.java
+++ b/core/java/android/bluetooth/BluetoothVolumeControl.java
@@ -17,6 +17,8 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.Manifest;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -27,18 +29,18 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.CloseGuard;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the public APIs to control the Bluetooth Volume Control service.
@@ -88,7 +90,7 @@
IBluetoothVolumeControl.class.getName()) {
@Override
public IBluetoothVolumeControl getServiceInterface(IBinder service) {
- return IBluetoothVolumeControl.Stub.asInterface(Binder.allowBlocking(service));
+ return IBluetoothVolumeControl.Stub.asInterface(service);
}
};
@@ -136,17 +138,23 @@
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (DBG) log("getConnectedDevices()");
final IBluetoothVolumeControl service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getConnectedDevices(mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getConnectedDevices(mAttributionSource), mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -161,18 +169,23 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) log("getDevicesMatchingStates()");
final IBluetoothVolumeControl service = getService();
- if (service != null && isEnabled()) {
+ final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
try {
+ final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+ new SynchronousResultReceiver();
+ service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
return Attributable.setAttributionSource(
- service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return new ArrayList<BluetoothDevice>();
+ return defaultValue;
}
/**
@@ -187,16 +200,20 @@
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getConnectionState(" + device + ")");
final IBluetoothVolumeControl service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionState(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.STATE_DISCONNECTED;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionState(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
+ return defaultValue;
}
/**
@@ -214,18 +231,19 @@
})
public void setVolume(@Nullable BluetoothDevice device,
@IntRange(from = 0, to = 255) int volume) {
- if (DBG)
- log("setVolume(" + volume + ")");
+ if (DBG) log("setVolume(" + volume + ")");
final IBluetoothVolumeControl service = getService();
- try {
- if (service != null && isEnabled()) {
- service.setVolume(device, volume, mAttributionSource);
- return;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.setVolume(device, volume, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
- if (service == null)
- Log.w(TAG, "Proxy not attached to service");
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
}
}
@@ -251,20 +269,22 @@
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothVolumeControl service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
- if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
- && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- return false;
- }
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)
+ && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
try {
- return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return false;
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return false;
+ return defaultValue;
}
/**
@@ -287,16 +307,20 @@
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothVolumeControl service = getService();
- if (service != null && isEnabled() && isValidDevice(device)) {
+ final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
try {
- return service.getConnectionPolicy(device, mAttributionSource);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(new Throwable()));
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getConnectionPolicy(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
- if (service == null) Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return defaultValue;
}
private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index ee173db..540e5a7 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -23,6 +23,7 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.PendingIntent;
+import android.bluetooth.Attributable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.IBluetoothGatt;
@@ -30,7 +31,6 @@
import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
import android.bluetooth.annotations.RequiresBluetoothScanPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.os.Handler;
import android.os.Looper;
@@ -514,16 +514,27 @@
@Override
public void onScanResult(final ScanResult scanResult) {
Attributable.setAttributionSource(scanResult, mAttributionSource);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onScanResult() - mScannerId=" + mScannerId);
+ }
if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
// Check null in case the scan has been stopped
synchronized (this) {
- if (mScannerId <= 0) return;
+ if (mScannerId <= 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Ignoring result as scan stopped.");
+ }
+ return;
+ };
}
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onScanResult() - handler run");
+ }
mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
}
});
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
index dea686d..bbd3117 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
@@ -18,6 +18,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
+import android.bluetooth.Attributable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothGatt;
@@ -25,7 +26,6 @@
import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
import android.bluetooth.annotations.RequiresBluetoothScanPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.os.Handler;
import android.os.Looper;
diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java
index 5228456..f437d86 100644
--- a/core/java/android/bluetooth/le/ScanResult.java
+++ b/core/java/android/bluetooth/le/ScanResult.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.bluetooth.Attributable;
import android.bluetooth.BluetoothDevice;
-import android.content.Attributable;
import android.content.AttributionSource;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 6ae2bb5..157e709 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
+import android.app.AppGlobals;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
@@ -191,10 +192,42 @@
return new ScopedParcelState(this);
}
- /** @hide */
- public static AttributionSource myAttributionSource() {
- return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(),
- /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null);
+ /**
+ * Returns a generic {@link AttributionSource} that represents the entire
+ * calling process.
+ *
+ * <p>Callers are <em>strongly</em> encouraged to use a more specific
+ * attribution source whenever possible, such as from
+ * {@link Context#getAttributionSource()}, since that enables developers to
+ * have more detailed and scoped control over attribution within
+ * sub-components of their app.
+ *
+ * @see Context#createAttributionContext(String)
+ * @see Context#getAttributionTag()
+ * @return a generic {@link AttributionSource} representing the entire
+ * calling process
+ * @throws IllegalStateException when no accurate {@link AttributionSource}
+ * can be determined
+ */
+ public static @NonNull AttributionSource myAttributionSource() {
+
+ final AttributionSource globalSource = ActivityThread.currentAttributionSource();
+ if (globalSource != null) {
+ return globalSource;
+ }
+
+ int uid = Process.myUid();
+ if (uid == Process.ROOT_UID) {
+ uid = Process.SYSTEM_UID;
+ }
+ try {
+ return new AttributionSource.Builder(uid)
+ .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
+ .build();
+ } catch (Exception ignored) {
+ }
+
+ throw new IllegalStateException("Failed to resolve AttributionSource");
}
/**
@@ -247,7 +280,7 @@
* whether the attribution source is one for the calling app to prevent the caller
* to pass you a source from another app without including themselves in the
* attribution chain.
- *f
+ *
* @return if the attribution source cannot be trusted to be from the caller.
*/
public boolean checkCallingUid() {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ceba01ec..98e124d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3286,7 +3286,7 @@
* apps targeting SDK Version {@link android.os.Build.VERSION_CODES#O}
* or higher are not allowed to start background services from the background.
* See
- * <a href="{@docRoot}/about/versions/oreo/background">
+ * <a href="/about/versions/oreo/background">
* Background Execution Limits</a>
* for more details.
*
@@ -3295,7 +3295,7 @@
* apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
* or higher are not allowed to start foreground services from the background.
* See
- * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+ * <a href="/about/versions/12/behavior-changes-12">
* Behavior changes: Apps targeting Android 12
* </a>
* for more details.
@@ -3349,7 +3349,7 @@
* apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
* or higher are not allowed to start foreground services from the background.
* See
- * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+ * <a href="/about/versions/12/behavior-changes-12">
* Behavior changes: Apps targeting Android 12
* </a>
* for more details.
@@ -6032,6 +6032,10 @@
* more general access to the URI's content provider then this check will
* always fail.
*
+ * <strong>Note:</strong> On SDK Version {@link android.os.Build.VERSION_CODES#S},
+ * calling this method from a secondary-user's context will incorrectly return
+ * {@link PackageManager#PERMISSION_DENIED} for all {code uris}.
+ *
* @param uris The list of URIs that is being checked.
* @param pid The process ID being checked against. Must be > 0.
* @param uid The user ID being checked against. A uid of 0 is the root
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e781c2f..9659df6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2742,6 +2742,22 @@
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
+ /**
+ * Broadcast Action: One of the suspend conditions have been modified for the packages.
+ * <p>Includes the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been modified
+ * <li> {@link #EXTRA_CHANGED_UID_LIST} is the set of uids which have been modified
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system. It is only sent to registered receivers.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGES_SUSPENSION_CHANGED =
+ "android.intent.action.PACKAGES_SUSPENSION_CHANGED";
/**
* Broadcast Action: Distracting packages have been changed.
@@ -5392,7 +5408,9 @@
*
* <p>Targets provided in this way will be presented inline with all other targets provided
* by services from other apps. They will be prioritized before other service targets, but
- * after those targets provided by sources that the user has manually pinned to the front.</p>
+ * after those targets provided by sources that the user has manually pinned to the front.
+ * You can provide up to two targets on this extra (the limit of two targets
+ * starts in Android 10).</p>
*
* @see #ACTION_CHOOSER
*/
@@ -5503,9 +5521,11 @@
/**
* A Parcelable[] of {@link Intent} or
* {@link android.content.pm.LabeledIntent} objects as set with
- * {@link #putExtra(String, Parcelable[])} of additional activities to place
- * a the front of the list of choices, when shown to the user with a
- * {@link #ACTION_CHOOSER}.
+ * {@link #putExtra(String, Parcelable[])} to place
+ * at the front of the list of choices, when shown to the user with an
+ * {@link #ACTION_CHOOSER}. You can choose up to two additional activities
+ * to show before the app suggestions (the limit of two additional activities starts in
+ * Android 10).
*/
public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
diff --git a/core/java/android/content/integrity/OWNERS b/core/java/android/content/integrity/OWNERS
index 20c758a..a1fe59c 100644
--- a/core/java/android/content/integrity/OWNERS
+++ b/core/java/android/content/integrity/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 722021
-toddke@android.com
-toddke@google.com
patb@google.com
diff --git a/core/java/android/content/om/OWNERS b/core/java/android/content/om/OWNERS
index 3669817..9c47336 100644
--- a/core/java/android/content/om/OWNERS
+++ b/core/java/android/content/om/OWNERS
@@ -1,6 +1,4 @@
# Bug component: 568631
-toddke@android.com
-toddke@google.com
patb@google.com
zyy@google.com
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 4e674f6..128bfb9 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -1,11 +1,9 @@
# Bug component: 36137
-toddke@android.com
-toddke@google.com
patb@google.com
per-file PackageParser.java = set noparent
-per-file PackageParser.java = chiuwinson@google.com,patb@google.com,toddke@google.com
+per-file PackageParser.java = chiuwinson@google.com,patb@google.com
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
diff --git a/core/java/android/content/pm/dex/OWNERS b/core/java/android/content/pm/dex/OWNERS
index 267e5d58..b590f659 100644
--- a/core/java/android/content/pm/dex/OWNERS
+++ b/core/java/android/content/pm/dex/OWNERS
@@ -1,7 +1,5 @@
# Bug component: 86431
-toddke@android.com
-toddke@google.com
patb@google.com
calin@google.com
ngeoffray@google.com
diff --git a/core/java/android/content/pm/parsing/OWNERS b/core/java/android/content/pm/parsing/OWNERS
index 8049d5c..445a833 100644
--- a/core/java/android/content/pm/parsing/OWNERS
+++ b/core/java/android/content/pm/parsing/OWNERS
@@ -2,4 +2,3 @@
chiuwinson@google.com
patb@google.com
-toddke@google.com
diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS
index cf7e689..f9c51dd 100644
--- a/core/java/android/content/pm/permission/OWNERS
+++ b/core/java/android/content/pm/permission/OWNERS
@@ -2,7 +2,5 @@
include platform/frameworks/base:/core/java/android/permission/OWNERS
-toddke@android.com
-toddke@google.com
patb@google.com
diff --git a/core/java/android/content/pm/split/OWNERS b/core/java/android/content/pm/split/OWNERS
index 3d126d2..b8fa1a9 100644
--- a/core/java/android/content/pm/split/OWNERS
+++ b/core/java/android/content/pm/split/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 36137
-toddke@android.com
-toddke@google.com
patb@google.com
diff --git a/core/java/android/content/pm/verify/domain/OWNERS b/core/java/android/content/pm/verify/domain/OWNERS
index c669112..445a833 100644
--- a/core/java/android/content/pm/verify/domain/OWNERS
+++ b/core/java/android/content/pm/verify/domain/OWNERS
@@ -2,4 +2,3 @@
chiuwinson@google.com
patb@google.com
-toddke@google.com
\ No newline at end of file
diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS
index d12d920..7460a14 100644
--- a/core/java/android/content/res/OWNERS
+++ b/core/java/android/content/res/OWNERS
@@ -1,6 +1,4 @@
# Bug component: 568761
-toddke@android.com
-toddke@google.com
patb@google.com
zyy@google.com
diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS
index 95f13b5..4143bfc 100644
--- a/core/java/android/hardware/OWNERS
+++ b/core/java/android/hardware/OWNERS
@@ -1,5 +1,5 @@
# Camera
-per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,yinchiayeh@google.com,zhijunhe@google.com,jchowdhary@google.com
+per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,zhijunhe@google.com,jchowdhary@google.com
# Sensor Privacy
per-file *SensorPrivacy* = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 3f3db29..c8c122d 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -438,9 +438,16 @@
}
private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelAuthentication();
+ Log.d(TAG, "Cancel BP authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
@@ -853,10 +860,12 @@
* @param userId The user to authenticate
* @param operationId The keystore operation associated with authentication
*
+ * @return A requestId that can be used to cancel this operation.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void authenticateUserForOperation(
+ public long authenticateUserForOperation(
@NonNull CancellationSignal cancel,
@NonNull @CallbackExecutor Executor executor,
@NonNull AuthenticationCallback callback,
@@ -871,7 +880,8 @@
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
- authenticateInternal(operationId, cancel, executor, callback, userId);
+
+ return authenticateInternal(operationId, cancel, executor, callback, userId);
}
/**
@@ -1002,10 +1012,10 @@
authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
}
- private void cancelAuthentication() {
+ private void cancelAuthentication(long requestId) {
if (mService != null) {
try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to cancel authentication", e);
}
@@ -1024,7 +1034,7 @@
authenticateInternal(operationId, cancel, executor, callback, userId);
}
- private void authenticateInternal(
+ private long authenticateInternal(
long operationId,
@NonNull CancellationSignal cancel,
@NonNull @CallbackExecutor Executor executor,
@@ -1040,9 +1050,7 @@
try {
if (cancel.isCanceled()) {
Log.w(TAG, "Authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener());
+ return -1;
}
mExecutor = executor;
@@ -1065,14 +1073,16 @@
promptInfo = mPromptInfo;
}
- mService.authenticate(mToken, operationId, userId, mBiometricServiceReceiver,
- mContext.getOpPackageName(), promptInfo);
-
+ final long authId = mService.authenticate(mToken, operationId, userId,
+ mBiometricServiceReceiver, mContext.getOpPackageName(), promptInfo);
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ return authId;
} catch (RemoteException e) {
Log.e(TAG, "Remote exception while authenticating", e);
mExecutor.execute(() -> callback.onAuthenticationError(
BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
mContext.getString(R.string.biometric_error_hw_unavailable)));
+ return -1;
}
}
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 4c2a9ae..91f794c 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -41,13 +41,14 @@
// Retrieve the package where BIometricOrompt's UI is implemented
String getUiPackage();
- // Requests authentication. The service choose the appropriate biometric to use, and show
- // the corresponding BiometricDialog.
- void authenticate(IBinder token, long sessionId, int userId,
+ // Requests authentication. The service chooses the appropriate biometric to use, and shows
+ // the corresponding BiometricDialog. A requestId is returned that can be used to cancel
+ // this operation.
+ long authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, in PromptInfo promptInfo);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
// TODO(b/141025588): Make userId the first arg to be consistent with hasEnrolledBiometrics.
// Checks if biometrics can be used.
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 876513f..addd622 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -48,13 +48,13 @@
// startPreparedClient().
void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication);
+ long requestId, int cookie, boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int cookie);
- // Cancels authentication.
- void cancelAuthenticationFromService(IBinder token, String opPackageName);
+ // Cancels authentication for the given requestId.
+ void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId);
// Determine if HAL is loaded and ready
boolean isHardwareDetected(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 64b5118..2c3c8c3 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -36,13 +36,14 @@
// Retrieve static sensor properties for all biometric sensors
List<SensorPropertiesInternal> getSensorProperties(String opPackageName);
- // Requests authentication. The service choose the appropriate biometric to use, and show
- // the corresponding BiometricDialog.
- void authenticate(IBinder token, long operationId, int userId,
+ // Requests authentication. The service chooses the appropriate biometric to use, and shows
+ // the corresponding BiometricDialog. A requestId is returned that can be used to cancel
+ // this operation.
+ long authenticate(IBinder token, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, in PromptInfo promptInfo);
- // Cancel authentication for the given session.
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
// Checks if biometrics can be used.
int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators);
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 5cfba3d..395c655 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -263,12 +263,12 @@
@Override
public void onServiceConnected(ComponentName component, IBinder binder) {
mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
- mInitFuture.setStatus(true);
try {
mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
} catch (RemoteException e) {
Log.e(TAG, "Remote IPC failed!");
}
+ mInitFuture.setStatus(true);
}
};
ctx.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT |
diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java
index 20ca4a3..032ed7e 100644
--- a/core/java/android/hardware/camera2/CaptureFailure.java
+++ b/core/java/android/hardware/camera2/CaptureFailure.java
@@ -59,7 +59,7 @@
private final CaptureRequest mRequest;
private final int mReason;
- private final boolean mDropped;
+ private final boolean mWasImageCaptured;
private final int mSequenceId;
private final long mFrameNumber;
private final String mErrorPhysicalCameraId;
@@ -68,10 +68,11 @@
* @hide
*/
public CaptureFailure(CaptureRequest request, int reason,
- boolean dropped, int sequenceId, long frameNumber, String errorPhysicalCameraId) {
+ boolean wasImageCaptured, int sequenceId, long frameNumber,
+ String errorPhysicalCameraId) {
mRequest = request;
mReason = reason;
- mDropped = dropped;
+ mWasImageCaptured = wasImageCaptured;
mSequenceId = sequenceId;
mFrameNumber = frameNumber;
mErrorPhysicalCameraId = errorPhysicalCameraId;
@@ -141,7 +142,7 @@
* @return boolean True if the image was captured, false otherwise.
*/
public boolean wasImageCaptured() {
- return !mDropped;
+ return mWasImageCaptured;
}
/**
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 8da6551..b8443fb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -873,21 +873,19 @@
@Override
public int submitBurst(List<Request> requests, IRequestCallback callback) {
int seqId = -1;
- synchronized (mInterfaceLock) {
- try {
- CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
- ArrayList<CaptureRequest> captureRequests = new ArrayList<>();
- for (Request request : requests) {
- captureRequests.add(initializeCaptureRequest(mCameraDevice, request,
- mCameraConfigMap));
- }
- seqId = mCaptureSession.captureBurstRequests(captureRequests,
- new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed to submit capture requests!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
+ try {
+ CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
+ ArrayList<CaptureRequest> captureRequests = new ArrayList<>();
+ for (Request request : requests) {
+ captureRequests.add(initializeCaptureRequest(mCameraDevice, request,
+ mCameraConfigMap));
}
+ seqId = mCaptureSession.captureBurstRequests(captureRequests,
+ new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to submit capture requests!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
return seqId;
@@ -896,18 +894,16 @@
@Override
public int setRepeating(Request request, IRequestCallback callback) {
int seqId = -1;
- synchronized (mInterfaceLock) {
- try {
- CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice,
- request, mCameraConfigMap);
- CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
- seqId = mCaptureSession.setSingleRepeatingRequest(repeatingRequest,
- new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed to enable repeating request!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice,
+ request, mCameraConfigMap);
+ CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
+ seqId = mCaptureSession.setSingleRepeatingRequest(repeatingRequest,
+ new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to enable repeating request!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
return seqId;
@@ -915,27 +911,23 @@
@Override
public void abortCaptures() {
- synchronized (mInterfaceLock) {
- try {
- mCaptureSession.abortCaptures();
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed during capture abort!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ mCaptureSession.abortCaptures();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed during capture abort!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
}
@Override
public void stopRepeating() {
- synchronized (mInterfaceLock) {
- try {
- mCaptureSession.stopRepeating();
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed during repeating capture stop!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ mCaptureSession.stopRepeating();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed during repeating capture stop!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index fc728a2..8864939 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -104,6 +104,9 @@
private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
new SparseArray<CaptureCallbackHolder>();
+ /** map request IDs which have batchedOutputs to requestCount*/
+ private HashMap<Integer, Integer> mBatchOutputMap = new HashMap<>();
+
private int mRepeatingRequestId = REQUEST_ID_NONE;
// Latest repeating request list's types
private int[] mRepeatingRequestTypes;
@@ -973,6 +976,7 @@
mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(REQUEST_ID_NONE, null);
mIdle = true;
mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>();
+ mBatchOutputMap = new HashMap<>();
mFrameNumberTracker = new FrameNumberTracker();
mCurrentSession.closeWithoutDraining();
@@ -1179,6 +1183,41 @@
return requestTypes;
}
+ private boolean hasBatchedOutputs(List<CaptureRequest> requestList) {
+ boolean hasBatchedOutputs = true;
+ for (int i = 0; i < requestList.size(); i++) {
+ CaptureRequest request = requestList.get(i);
+ if (!request.isPartOfCRequestList()) {
+ hasBatchedOutputs = false;
+ break;
+ }
+ if (i == 0) {
+ Collection<Surface> targets = request.getTargets();
+ if (targets.size() != 2) {
+ hasBatchedOutputs = false;
+ break;
+ }
+ }
+ }
+ return hasBatchedOutputs;
+ }
+
+ private void updateTracker(int requestId, long frameNumber,
+ int requestType, CaptureResult result, boolean isPartialResult) {
+ int requestCount = 1;
+ // If the request has batchedOutputs update each frame within the batch.
+ if (mBatchOutputMap.containsKey(requestId)) {
+ requestCount = mBatchOutputMap.get(requestId);
+ for (int i = 0; i < requestCount; i++) {
+ mFrameNumberTracker.updateTracker(frameNumber - (requestCount - 1 - i),
+ result, isPartialResult, requestType);
+ }
+ } else {
+ mFrameNumberTracker.updateTracker(frameNumber, result,
+ isPartialResult, requestType);
+ }
+ }
+
private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,
Executor executor, boolean repeating) throws CameraAccessException {
@@ -1224,6 +1263,14 @@
request.recoverStreamIdToSurface();
}
+ // If the request has batched outputs, then store the
+ // requestCount and requestId in the map.
+ boolean hasBatchedOutputs = hasBatchedOutputs(requestList);
+ if (hasBatchedOutputs) {
+ int requestCount = requestList.size();
+ mBatchOutputMap.put(requestInfo.getRequestId(), requestCount);
+ }
+
if (callback != null) {
mCaptureCallbackMap.put(requestInfo.getRequestId(),
new CaptureCallbackHolder(
@@ -1820,7 +1867,7 @@
final CaptureFailure failure = new CaptureFailure(
request,
reason,
- /*dropped*/ mayHaveBuffers,
+ mayHaveBuffers,
requestId,
frameNumber,
errorPhysicalCameraId);
@@ -1839,8 +1886,18 @@
if (DEBUG) {
Log.v(TAG, String.format("got error frame %d", frameNumber));
}
- mFrameNumberTracker.updateTracker(frameNumber,
- /*error*/true, request.getRequestType());
+
+ // Update FrameNumberTracker for every frame during HFR mode.
+ if (mBatchOutputMap.containsKey(requestId)) {
+ for (int i = 0; i < mBatchOutputMap.get(requestId); i++) {
+ mFrameNumberTracker.updateTracker(frameNumber - (subsequenceId - i),
+ /*error*/true, request.getRequestType());
+ }
+ } else {
+ mFrameNumberTracker.updateTracker(frameNumber,
+ /*error*/true, request.getRequestType());
+ }
+
checkAndFireSequenceComplete();
// Dispatch the failure callback
@@ -2023,7 +2080,6 @@
public void onResultReceived(CameraMetadataNative result,
CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
throws RemoteException {
-
int requestId = resultExtras.getRequestId();
long frameNumber = resultExtras.getFrameNumber();
@@ -2064,8 +2120,8 @@
+ frameNumber);
}
- mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, /*result*/null,
+ isPartialResult);
return;
}
@@ -2077,8 +2133,9 @@
+ frameNumber);
}
- mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, /*result*/null,
+ isPartialResult);
+
return;
}
@@ -2184,9 +2241,7 @@
Binder.restoreCallingIdentity(ident);
}
- // Collect the partials for a total result; or mark the frame as totally completed
- mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, finalResult, isPartialResult);
// Fire onCaptureSequenceCompleted
if (!isPartialResult) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 3b1cb94..425f22c 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -58,7 +58,7 @@
private static final class JpegParameters {
public HashSet<Long> mTimeStamps = new HashSet<>();
- public int mRotation = JPEG_DEFAULT_ROTATION; // CCW multiple of 90 degrees
+ public int mRotation = JPEG_DEFAULT_ROTATION; // CW multiple of 90 degrees
public int mQuality = JPEG_DEFAULT_QUALITY; // [0..100]
}
@@ -100,7 +100,8 @@
Integer orientation = captureBundles.get(0).captureResult.get(
CaptureResult.JPEG_ORIENTATION);
if (orientation != null) {
- ret.mRotation = orientation / 90;
+ // The jpeg encoder expects CCW rotation, convert from CW
+ ret.mRotation = (360 - (orientation % 360)) / 90;
} else {
Log.w(TAG, "No jpeg rotation set, using default: " + JPEG_DEFAULT_ROTATION);
}
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index c5d37c2..0dc8f92 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -60,12 +60,18 @@
/** Brightness */
public final float brightness;
+ /** Brightness after {@link DisplayPowerController} adjustments */
+ public final float adjustedBrightness;
+
/** Current minimum supported brightness. */
public final float brightnessMinimum;
/** Current maximum supported brightness. */
public final float brightnessMaximum;
+ /** Brightness values greater than this point are only used in High Brightness Mode. */
+ public final float highBrightnessTransitionPoint;
+
/**
* Current state of high brightness mode.
* Can be any of HIGH_BRIGHTNESS_MODE_* values.
@@ -73,11 +79,20 @@
public final int highBrightnessMode;
public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
- @HighBrightnessMode int highBrightnessMode) {
+ @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint) {
+ this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode,
+ highBrightnessTransitionPoint);
+ }
+
+ public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum,
+ float brightnessMaximum, @HighBrightnessMode int highBrightnessMode,
+ float highBrightnessTransitionPoint) {
this.brightness = brightness;
+ this.adjustedBrightness = adjustedBrightness;
this.brightnessMinimum = brightnessMinimum;
this.brightnessMaximum = brightnessMaximum;
this.highBrightnessMode = highBrightnessMode;
+ this.highBrightnessTransitionPoint = highBrightnessTransitionPoint;
}
/**
@@ -103,9 +118,11 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(brightness);
+ dest.writeFloat(adjustedBrightness);
dest.writeFloat(brightnessMinimum);
dest.writeFloat(brightnessMaximum);
dest.writeInt(highBrightnessMode);
+ dest.writeFloat(highBrightnessTransitionPoint);
}
public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
@@ -123,9 +140,11 @@
private BrightnessInfo(Parcel source) {
brightness = source.readFloat();
+ adjustedBrightness = source.readFloat();
brightnessMinimum = source.readFloat();
brightnessMaximum = source.readFloat();
highBrightnessMode = source.readInt();
+ highBrightnessTransitionPoint = source.readFloat();
}
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index e13a7b6..fc8337a 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1257,6 +1257,23 @@
*/
String KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS =
"fixed_refresh_rate_high_ambient_brightness_thresholds";
+
+ /**
+ * Key for refresh rate when the device is in high brightness mode for sunlight visility.
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.integer#config_defaultRefreshRateInHbmSunlight
+ */
+ String KEY_REFRESH_RATE_IN_HBM_SUNLIGHT = "refresh_rate_in_hbm_sunlight";
+
+ /**
+ * Key for refresh rate when the device is in high brightness mode for HDR.
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.integer#config_defaultRefreshRateInHbmHdr
+ */
+ String KEY_REFRESH_RATE_IN_HBM_HDR = "refresh_rate_in_hbm_hdr";
+
/**
* Key for default peak refresh rate
*
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a9b95fc..07babb1 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -361,6 +361,11 @@
for (int i = 0; i < numListeners; i++) {
mask |= mDisplayListeners.get(i).mEventsMask;
}
+ if (mDispatchNativeCallbacks) {
+ mask |= DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ }
return mask;
}
@@ -908,7 +913,7 @@
private static final class DisplayListenerDelegate extends Handler {
public final DisplayListener mListener;
- public long mEventsMask;
+ public volatile long mEventsMask;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
@@ -928,12 +933,12 @@
removeCallbacksAndMessages(null);
}
- public synchronized void setEventsMask(@EventsMask long newEventsMask) {
+ public void setEventsMask(@EventsMask long newEventsMask) {
mEventsMask = newEventsMask;
}
@Override
- public synchronized void handleMessage(Message msg) {
+ public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_DISPLAY_ADDED:
if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
@@ -1047,12 +1052,17 @@
private static native void nSignalNativeCallbacks(float refreshRate);
- // Called from AChoreographer via JNI.
- // Registers AChoreographer so that refresh rate callbacks can be dispatched from DMS.
- private void registerNativeChoreographerForRefreshRateCallbacks() {
+ /**
+ * Called from AChoreographer via JNI.
+ * Registers AChoreographer so that refresh rate callbacks can be dispatched from DMS.
+ * Public for unit testing to be able to call this method.
+ */
+ @VisibleForTesting
+ public void registerNativeChoreographerForRefreshRateCallbacks() {
synchronized (mLock) {
- registerCallbackIfNeededLocked();
mDispatchNativeCallbacks = true;
+ registerCallbackIfNeededLocked();
+ updateCallbackIfNeededLocked();
DisplayInfo display = getDisplayInfoLocked(Display.DEFAULT_DISPLAY);
if (display != null) {
// We need to tell AChoreographer instances the current refresh rate so that apps
@@ -1063,11 +1073,16 @@
}
}
- // Called from AChoreographer via JNI.
- // Unregisters AChoreographer from receiving refresh rate callbacks.
- private void unregisterNativeChoreographerForRefreshRateCallbacks() {
+ /**
+ * Called from AChoreographer via JNI.
+ * Unregisters AChoreographer from receiving refresh rate callbacks.
+ * Public for unit testing to be able to call this method.
+ */
+ @VisibleForTesting
+ public void unregisterNativeChoreographerForRefreshRateCallbacks() {
synchronized (mLock) {
mDispatchNativeCallbacks = false;
+ updateCallbackIfNeededLocked();
}
}
}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 385ad2d..56f8142 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -58,7 +58,7 @@
public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
private static final String TAG = "FaceManager";
- private static final boolean DEBUG = true;
+
private static final int MSG_ENROLL_RESULT = 100;
private static final int MSG_ACQUIRED = 101;
private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -207,13 +207,9 @@
throw new IllegalArgumentException("Must supply an authentication callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "authentication already canceled");
+ return;
}
if (mService != null) {
@@ -223,17 +219,18 @@
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
- mService.authenticate(mToken, operationId, userId, mServiceReceiver,
- mContext.getOpPackageName(), isKeyguardBypassEnabled);
+ final long authId = mService.authenticate(mToken, operationId, userId,
+ mServiceReceiver, mContext.getOpPackageName(), isKeyguardBypassEnabled);
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception while authenticating: ", e);
- if (callback != null) {
- // Though this may not be a hardware issue, it will cause apps to give up or
- // try again later.
- callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
- getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */));
- }
+ // Though this may not be a hardware issue, it will cause apps to give up or
+ // try again later.
+ callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
+ getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */));
} finally {
Trace.endSection();
}
@@ -255,14 +252,14 @@
if (cancel.isCanceled()) {
Slog.w(TAG, "Detection already cancelled");
return;
- } else {
- cancel.setOnCancelListener(new OnFaceDetectionCancelListener());
}
mFaceDetectionCallback = callback;
try {
- mService.detectFace(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final long authId = mService.detectFace(
+ mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
}
@@ -726,23 +723,23 @@
}
}
- private void cancelAuthentication(CryptoObject cryptoObject) {
+ private void cancelAuthentication(long requestId) {
if (mService != null) {
try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
- private void cancelFaceDetect() {
+ private void cancelFaceDetect(long requestId) {
if (mService == null) {
return;
}
try {
- mService.cancelFaceDetect(mToken, mContext.getOpPackageName());
+ mService.cancelFaceDetect(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -794,9 +791,9 @@
// This is used as a last resort in case a vendor string is missing
// It should not happen for anything other than FACE_ERROR_VENDOR, but
// warn and use the default if all else fails.
- // TODO(b/196639965): update string
Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
- return "";
+ return context.getString(
+ com.android.internal.R.string.face_error_vendor_unknown);
}
/**
@@ -1110,22 +1107,30 @@
}
private class OnAuthenticationCancelListener implements OnCancelListener {
- private final CryptoObject mCrypto;
+ private final long mAuthRequestId;
- OnAuthenticationCancelListener(CryptoObject crypto) {
- mCrypto = crypto;
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
}
@Override
public void onCancel() {
- cancelAuthentication(mCrypto);
+ Slog.d(TAG, "Cancel face authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
private class OnFaceDetectionCancelListener implements OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnFaceDetectionCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelFaceDetect();
+ Slog.d(TAG, "Cancel face detect requested for: " + mAuthRequestId);
+ cancelFaceDetect(mAuthRequestId);
}
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index db02a0ef..e919824 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -44,34 +44,36 @@
// Retrieve static sensor properties for the specified sensor
FaceSensorPropertiesInternal getSensorProperties(int sensorId, String opPackageName);
- // Authenticate the given sessionId with a face
- void authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
+ // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
+ long authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
String opPackageName, boolean isKeyguardBypassEnabled);
// Uses the face hardware to detect for the presence of a face, without giving details
- // about accept/reject/lockout.
- void detectFace(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
+ // about accept/reject/lockout. A requestId is returned that can be used to cancel this
+ // operation.
+ long detectFace(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
// This method prepares the service to start authenticating, but doesn't start authentication.
// This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
// called from BiometricService. The additional uid, pid, userId arguments should be determined
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
- void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token, long operationId,
- int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication);
+ void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token,
+ long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
+ String opPackageName, long requestId, int cookie,
+ boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int sensorId, int cookie);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
- // Cancel face detection
- void cancelFaceDetect(IBinder token, String opPackageName);
+ // Cancel face detection for the given requestId.
+ void cancelFaceDetect(IBinder token, String opPackageName, long requestId);
// Same as above, with extra arguments.
- void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName);
+ void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start face enrollment
void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 87d45b9..a3d595c 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -146,6 +146,7 @@
private CryptoObject mCryptoObject;
@Nullable private RemoveTracker mRemoveTracker;
private Handler mHandler;
+ @Nullable private float[] mEnrollStageThresholds;
/**
* Retrieves a list of properties for all fingerprint sensors on the device.
@@ -189,22 +190,30 @@
}
private class OnAuthenticationCancelListener implements OnCancelListener {
- private android.hardware.biometrics.CryptoObject mCrypto;
+ private final long mAuthRequestId;
- public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) {
- mCrypto = crypto;
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
}
@Override
public void onCancel() {
- cancelAuthentication(mCrypto);
+ Slog.d(TAG, "Cancel fingerprint authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
private class OnFingerprintDetectionCancelListener implements OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnFingerprintDetectionCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelFingerprintDetect();
+ Slog.d(TAG, "Cancel fingerprint detect requested for: " + mAuthRequestId);
+ cancelFingerprintDetect(mAuthRequestId);
}
}
@@ -552,13 +561,9 @@
throw new IllegalArgumentException("Must supply an authentication callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "authentication already canceled");
+ return;
}
if (mService != null) {
@@ -567,8 +572,11 @@
mAuthenticationCallback = callback;
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
- mService.authenticate(mToken, operationId, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final long authId = mService.authenticate(mToken, operationId, sensorId, userId,
+ mServiceReceiver, mContext.getOpPackageName());
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception while authenticating: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or try
@@ -595,15 +603,14 @@
if (cancel.isCanceled()) {
Slog.w(TAG, "Detection already cancelled");
return;
- } else {
- cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener());
}
mFingerprintDetectionCallback = callback;
try {
- mService.detectFingerprint(mToken, userId, mServiceReceiver,
+ final long authId = mService.detectFingerprint(mToken, userId, mServiceReceiver,
mContext.getOpPackageName());
+ cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
}
@@ -844,26 +851,6 @@
}
/**
- * Checks if the specified user has enrollments in any of the specified sensors.
- * @hide
- */
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public boolean hasEnrolledTemplatesForAnySensor(int userId,
- @NonNull List<FingerprintSensorPropertiesInternal> sensors) {
- if (mService == null) {
- Slog.w(TAG, "hasEnrolledTemplatesForAnySensor: no fingerprint service");
- return false;
- }
-
- try {
- return mService.hasEnrolledTemplatesForAnySensor(userId, sensors,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1320,21 +1307,21 @@
}
}
- private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) {
+ private void cancelAuthentication(long requestId) {
if (mService != null) try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- private void cancelFingerprintDetect() {
+ private void cancelFingerprintDetect(long requestId) {
if (mService == null) {
return;
}
try {
- mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName());
+ mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1343,6 +1330,46 @@
/**
* @hide
*/
+ public int getEnrollStageCount() {
+ if (mEnrollStageThresholds == null) {
+ mEnrollStageThresholds = createEnrollStageThresholds(mContext);
+ }
+ return mEnrollStageThresholds.length + 1;
+ }
+
+ /**
+ * @hide
+ */
+ public float getEnrollStageThreshold(int index) {
+ if (mEnrollStageThresholds == null) {
+ mEnrollStageThresholds = createEnrollStageThresholds(mContext);
+ }
+
+ if (index < 0 || index > mEnrollStageThresholds.length) {
+ Slog.w(TAG, "Unsupported enroll stage index: " + index);
+ return index < 0 ? 0f : 1f;
+ }
+
+ // The implicit threshold for the final stage is always 1.
+ return index == mEnrollStageThresholds.length ? 1f : mEnrollStageThresholds[index];
+ }
+
+ @NonNull
+ private static float[] createEnrollStageThresholds(@NonNull Context context) {
+ // TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config
+ final String[] enrollStageThresholdStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.config_udfps_enroll_stage_thresholds);
+
+ final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length];
+ for (int i = 0; i < enrollStageThresholds.length; i++) {
+ enrollStageThresholds[i] = Float.parseFloat(enrollStageThresholdStrings[i]);
+ }
+ return enrollStageThresholds;
+ }
+
+ /**
+ * @hide
+ */
public static String getErrorString(Context context, int errMsg, int vendorCode) {
switch (errMsg) {
case FINGERPRINT_ERROR_HW_UNAVAILABLE:
@@ -1390,9 +1417,9 @@
// This is used as a last resort in case a vendor string is missing
// It should not happen for anything other than FINGERPRINT_ERROR_VENDOR, but
// warn and use the default if all else fails.
- // TODO(b/196639965): update string
Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
- return "";
+ return context.getString(
+ com.android.internal.R.string.fingerprint_error_vendor_unknown);
}
/**
diff --git a/core/java/android/hardware/fingerprint/FingerprintStateListener.java b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
index 6e607a2..cf914c5 100644
--- a/core/java/android/hardware/fingerprint/FingerprintStateListener.java
+++ b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
@@ -49,5 +49,10 @@
* Defines behavior in response to state update
* @param newState new state of fingerprint sensor
*/
- public abstract void onStateChanged(@FingerprintStateListener.State int newState);
+ public void onStateChanged(@FingerprintStateListener.State int newState) {};
+
+ /**
+ * Invoked when enrollment state changes for the specified user
+ */
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {};
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 3979afe..de94b2f 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -48,15 +48,16 @@
// Retrieve static sensor properties for the specified sensor
FingerprintSensorPropertiesInternal getSensorProperties(int sensorId, String opPackageName);
- // Authenticate the given sessionId with a fingerprint. This is protected by
- // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes
- // through FingerprintManager now.
- void authenticate(IBinder token, long operationId, int sensorId, int userId,
+ // Authenticate with a fingerprint. This is protected by USE_FINGERPRINT/USE_BIOMETRIC
+ // permission. This is effectively deprecated, since it only comes through FingerprintManager
+ // now. A requestId is returned that can be used to cancel this operation.
+ long authenticate(IBinder token, long operationId, int sensorId, int userId,
IFingerprintServiceReceiver receiver, String opPackageName);
// Uses the fingerprint hardware to detect for the presence of a finger, without giving details
- // about accept/reject/lockout.
- void detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
+ // about accept/reject/lockout. A requestId is returned that can be used to cancel this
+ // operation.
+ long detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
String opPackageName);
// This method prepares the service to start authenticating, but doesn't start authentication.
@@ -65,21 +66,21 @@
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId,
- IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie,
- boolean allowBackgroundAuthentication);
+ IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+ int cookie, boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int sensorId, int cookie);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
- // Cancel finger detection
- void cancelFingerprintDetect(IBinder token, String opPackageName);
+ // Cancel finger detection for the given requestId.
+ void cancelFingerprintDetect(IBinder token, String opPackageName, long requestId);
// Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
// an additional uid, pid, userid.
- void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName);
+ void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start fingerprint enrollment
void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
@@ -119,9 +120,6 @@
// Determine if a user has at least one enrolled fingerprint.
boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName);
- // Determine if a user has at least one enrolled fingerprint in any of the specified sensors
- boolean hasEnrolledTemplatesForAnySensor(int userId, in List<FingerprintSensorPropertiesInternal> sensors, String opPackageName);
-
// Return the LockoutTracker status for the specified user
int getLockoutModeForUser(int sensorId, int userId);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
index 56dba7e..1aa6fa1 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
@@ -24,4 +24,5 @@
*/
oneway interface IFingerprintStateListener {
void onStateChanged(int newState);
+ void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments);
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 881e0cf..74cb42d 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1749,12 +1749,12 @@
if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) {
return false;
}
- if ((mInputEditorInfo != null
- && (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0)
+ if (mInputEditorInfo != null
+ && ((mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0
// If app window has portrait orientation, regardless of what display orientation
// is, IME shouldn't use fullscreen-mode.
|| (mInputEditorInfo.internalImeOptions
- & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0) {
+ & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0)) {
return false;
}
return true;
diff --git a/core/java/android/net/IInternalNetworkManagementListener.aidl b/core/java/android/net/IInternalNetworkManagementListener.aidl
new file mode 100644
index 0000000..69cde3b
--- /dev/null
+++ b/core/java/android/net/IInternalNetworkManagementListener.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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 android.net;
+
+import android.net.InternalNetworkManagementException;
+import android.net.Network;
+
+/** @hide */
+oneway interface IInternalNetworkManagementListener {
+ void onComplete(in Network network, in InternalNetworkManagementException exception);
+}
\ No newline at end of file
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index b18e9be..fab692c 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -142,8 +142,9 @@
boolean isBypassable,
boolean isMetered,
int maxMtu,
- boolean restrictToTestNetworks) {
- super(type);
+ boolean restrictToTestNetworks,
+ boolean excludeLocalRoutes) {
+ super(type, excludeLocalRoutes);
checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address");
checkNotNull(userIdentity, MISSING_PARAM_MSG_TMPL, "User Identity");
@@ -403,7 +404,8 @@
&& mIsBypassable == other.mIsBypassable
&& mIsMetered == other.mIsMetered
&& mMaxMtu == other.mMaxMtu
- && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks;
+ && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks
+ && mExcludeLocalRoutes == other.mExcludeLocalRoutes;
}
/**
@@ -417,7 +419,7 @@
@NonNull
public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException {
final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */,
- mIsRestrictedToTestNetworks);
+ mIsRestrictedToTestNetworks, mExcludeLocalRoutes);
profile.type = mType;
profile.server = mServerAddr;
profile.ipsecIdentifier = mUserIdentity;
@@ -518,6 +520,8 @@
throw new IllegalArgumentException("Invalid auth method set");
}
+ builder.setExcludeLocalRoutes(profile.excludeLocalRoutes);
+
return builder.build();
}
@@ -657,6 +661,7 @@
private boolean mIsMetered = true;
private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT;
private boolean mIsRestrictedToTestNetworks = false;
+ private boolean mExcludeLocalRoutes = false;
/**
* Creates a new builder with the basic parameters of an IKEv2/IPsec VPN.
@@ -902,6 +907,18 @@
}
/**
+ * Sets whether the local traffic is exempted from the VPN.
+ *
+ * @hide TODO(184750836): unhide once the implementation is completed
+ */
+ @NonNull
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ public Builder setExcludeLocalRoutes(boolean excludeLocalRoutes) {
+ mExcludeLocalRoutes = excludeLocalRoutes;
+ return this;
+ }
+
+ /**
* Validates, builds and provisions the VpnProfile.
*
* @throws IllegalArgumentException if any of the required keys or values were invalid
@@ -924,7 +941,8 @@
mIsBypassable,
mIsMetered,
mMaxMtu,
- mIsRestrictedToTestNetworks);
+ mIsRestrictedToTestNetworks,
+ mExcludeLocalRoutes);
}
}
}
diff --git a/core/java/android/net/InternalNetworkManagementException.aidl b/core/java/android/net/InternalNetworkManagementException.aidl
new file mode 100644
index 0000000..dcce706
--- /dev/null
+++ b/core/java/android/net/InternalNetworkManagementException.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net;
+
+ parcelable InternalNetworkManagementException;
\ No newline at end of file
diff --git a/core/java/android/net/InternalNetworkManagementException.java b/core/java/android/net/InternalNetworkManagementException.java
new file mode 100644
index 0000000..7f4e403
--- /dev/null
+++ b/core/java/android/net/InternalNetworkManagementException.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public final class InternalNetworkManagementException
+ extends RuntimeException implements Parcelable {
+
+ /* @hide */
+ public InternalNetworkManagementException(@NonNull final Throwable t) {
+ super(t);
+ }
+
+ private InternalNetworkManagementException(@NonNull final Parcel source) {
+ super(source.readString());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(getCause().getMessage());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<InternalNetworkManagementException> CREATOR =
+ new Parcelable.Creator<InternalNetworkManagementException>() {
+ @Override
+ public InternalNetworkManagementException[] newArray(int size) {
+ return new InternalNetworkManagementException[size];
+ }
+
+ @Override
+ public InternalNetworkManagementException createFromParcel(@NonNull Parcel source) {
+ return new InternalNetworkManagementException(source);
+ }
+ };
+}
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.aidl b/core/java/android/net/InternalNetworkUpdateRequest.aidl
new file mode 100644
index 0000000..da00cb9
--- /dev/null
+++ b/core/java/android/net/InternalNetworkUpdateRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net;
+
+ parcelable InternalNetworkUpdateRequest;
\ No newline at end of file
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/core/java/android/net/InternalNetworkUpdateRequest.java
new file mode 100644
index 0000000..6f09383
--- /dev/null
+++ b/core/java/android/net/InternalNetworkUpdateRequest.java
@@ -0,0 +1,108 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/** @hide */
+public final class InternalNetworkUpdateRequest implements Parcelable {
+ @NonNull
+ private final StaticIpConfiguration mIpConfig;
+ @Nullable
+ private final NetworkCapabilities mNetworkCapabilities;
+
+ @NonNull
+ public StaticIpConfiguration getIpConfig() {
+ return new StaticIpConfiguration(mIpConfig);
+ }
+
+ @NonNull
+ public NetworkCapabilities getNetworkCapabilities() {
+ return mNetworkCapabilities == null
+ ? null : new NetworkCapabilities(mNetworkCapabilities);
+ }
+
+ /** @hide */
+ public InternalNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig,
+ @Nullable final NetworkCapabilities networkCapabilities) {
+ Objects.requireNonNull(ipConfig);
+ mIpConfig = new StaticIpConfiguration(ipConfig);
+ if (null == networkCapabilities) {
+ mNetworkCapabilities = null;
+ } else {
+ mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
+ }
+ }
+
+ private InternalNetworkUpdateRequest(@NonNull final Parcel source) {
+ Objects.requireNonNull(source);
+ mIpConfig = StaticIpConfiguration.CREATOR.createFromParcel(source);
+ mNetworkCapabilities = NetworkCapabilities.CREATOR.createFromParcel(source);
+ }
+
+ @Override
+ public String toString() {
+ return "InternalNetworkUpdateRequest{"
+ + "mIpConfig=" + mIpConfig
+ + ", mNetworkCapabilities=" + mNetworkCapabilities + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InternalNetworkUpdateRequest that = (InternalNetworkUpdateRequest) o;
+
+ return Objects.equals(that.getIpConfig(), mIpConfig)
+ && Objects.equals(that.getNetworkCapabilities(), mNetworkCapabilities);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIpConfig, mNetworkCapabilities);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mIpConfig.writeToParcel(dest, flags);
+ mNetworkCapabilities.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<InternalNetworkUpdateRequest> CREATOR =
+ new Parcelable.Creator<InternalNetworkUpdateRequest>() {
+ @Override
+ public InternalNetworkUpdateRequest[] newArray(int size) {
+ return new InternalNetworkUpdateRequest[size];
+ }
+
+ @Override
+ public InternalNetworkUpdateRequest createFromParcel(@NonNull Parcel source) {
+ return new InternalNetworkUpdateRequest(source);
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 70fe5d6..8b3d49e 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -18,9 +18,11 @@
import static android.net.NetworkStats.METERED_ALL;
import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_BLUETOOTH;
import static android.net.NetworkTemplate.MATCH_CARRIER;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
import static android.net.NetworkTemplate.MATCH_MOBILE;
-import static android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,6 +44,7 @@
import java.time.ZonedDateTime;
import java.util.Iterator;
import java.util.Objects;
+import java.util.Set;
/**
* Policy for networks matching a {@link NetworkTemplate}, including usage cycle
@@ -59,15 +62,16 @@
* Initial Version of the NetworkTemplate backup serializer.
*/
private static final int TEMPLATE_BACKUP_VERSION_1_INIT = 1;
+ private static final int TEMPLATE_BACKUP_VERSION_2_UNSUPPORTED = 2;
/**
* Version of the NetworkTemplate backup serializer that added carrier template support.
*/
- private static final int TEMPLATE_BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE = 2;
+ private static final int TEMPLATE_BACKUP_VERSION_3_SUPPORT_CARRIER_TEMPLATE = 3;
/**
* Latest Version of the NetworkTemplate Backup Serializer.
*/
private static final int TEMPLATE_BACKUP_VERSION_LATEST =
- TEMPLATE_BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE;
+ TEMPLATE_BACKUP_VERSION_3_SUPPORT_CARRIER_TEMPLATE;
public static final int CYCLE_NONE = -1;
public static final long WARNING_DISABLED = -1;
@@ -324,7 +328,7 @@
@NonNull
private byte[] getNetworkTemplateBytesForBackup() throws IOException {
- if (!template.isPersistable()) {
+ if (!isTemplatePersistable(this.template)) {
Log.wtf(TAG, "Trying to backup non-persistable template: " + this);
}
@@ -334,10 +338,9 @@
out.writeInt(TEMPLATE_BACKUP_VERSION_LATEST);
out.writeInt(template.getMatchRule());
- BackupUtils.writeString(out, template.getSubscriberId());
- BackupUtils.writeString(out, template.getNetworkId());
+ BackupUtils.writeString(out, template.getSubscriberIds().iterator().next());
+ BackupUtils.writeString(out, template.getWifiNetworkKey());
out.writeInt(template.getMeteredness());
- out.writeInt(template.getSubscriberIdMatchRule());
return baos.toByteArray();
}
@@ -346,36 +349,61 @@
private static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in)
throws IOException, BackupUtils.BadVersionException {
int version = in.readInt();
- if (version < TEMPLATE_BACKUP_VERSION_1_INIT || version > TEMPLATE_BACKUP_VERSION_LATEST) {
+ if (version < TEMPLATE_BACKUP_VERSION_1_INIT || version > TEMPLATE_BACKUP_VERSION_LATEST
+ || version == TEMPLATE_BACKUP_VERSION_2_UNSUPPORTED) {
throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
}
int matchRule = in.readInt();
final String subscriberId = BackupUtils.readString(in);
- final String networkId = BackupUtils.readString(in);
+ final String wifiNetworkKey = BackupUtils.readString(in);
final int metered;
- final int subscriberIdMatchRule;
- if (version >= TEMPLATE_BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE) {
+ if (version >= TEMPLATE_BACKUP_VERSION_3_SUPPORT_CARRIER_TEMPLATE) {
metered = in.readInt();
- subscriberIdMatchRule = in.readInt();
} else {
// For backward compatibility, fill the missing filters from match rules.
- metered = (matchRule == MATCH_MOBILE
- || matchRule == NetworkTemplate.MATCH_MOBILE_WILDCARD
- || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL;
- subscriberIdMatchRule = SUBSCRIBER_ID_MATCH_RULE_EXACT;
+ metered = (matchRule == MATCH_MOBILE || matchRule == MATCH_CARRIER)
+ ? METERED_YES : METERED_ALL;
}
try {
- return new NetworkTemplate(matchRule,
- subscriberId, new String[]{subscriberId},
- networkId, metered, NetworkStats.ROAMING_ALL,
- NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL,
- NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule);
+ final NetworkTemplate.Builder builder = new NetworkTemplate.Builder(matchRule)
+ .setMeteredness(metered);
+ if (subscriberId != null) {
+ builder.setSubscriberIds(Set.of(subscriberId));
+ }
+ if (wifiNetworkKey != null) {
+ builder.setWifiNetworkKeys(Set.of(wifiNetworkKey));
+ }
+ return builder.build();
} catch (IllegalArgumentException e) {
throw new BackupUtils.BadVersionException(
"Restored network template contains unknown match rule " + matchRule, e);
}
}
+
+ /**
+ * Check if the template can be persisted into disk.
+ */
+ public static boolean isTemplatePersistable(@NonNull NetworkTemplate template) {
+ switch (template.getMatchRule()) {
+ case MATCH_BLUETOOTH:
+ case MATCH_ETHERNET:
+ return true;
+ case MATCH_CARRIER:
+ case MATCH_MOBILE:
+ return !template.getSubscriberIds().isEmpty();
+ case MATCH_WIFI:
+ if (template.getWifiNetworkKeys().isEmpty()
+ && template.getSubscriberIds().isEmpty()) {
+ return false;
+ }
+ return true;
+ default:
+ // Don't allow persistable for unknown types or legacy types such as
+ // MATCH_MOBILE_WILDCARD, MATCH_PROXY, etc.
+ return false;
+ }
+ }
}
diff --git a/core/java/android/net/PlatformVpnProfile.java b/core/java/android/net/PlatformVpnProfile.java
index 445ec91..777a90c 100644
--- a/core/java/android/net/PlatformVpnProfile.java
+++ b/core/java/android/net/PlatformVpnProfile.java
@@ -66,15 +66,30 @@
@PlatformVpnType protected final int mType;
/** @hide */
- PlatformVpnProfile(@PlatformVpnType int type) {
+ protected final boolean mExcludeLocalRoutes;
+
+ /** @hide */
+ PlatformVpnProfile(@PlatformVpnType int type, boolean excludeLocalRoutes) {
mType = type;
+ mExcludeLocalRoutes = excludeLocalRoutes;
}
+
/** Returns the profile integer type. */
@PlatformVpnType
public final int getType() {
return mType;
}
+
+ /**
+ * Returns if the local traffic is exempted from the VPN.
+ *
+ * @hide TODO(184750836): unhide once the implementation is completed
+ */
+ public final boolean getExcludeLocalRoutes() {
+ return mExcludeLocalRoutes;
+ }
+
/** Returns a type string describing the VPN profile type */
@NonNull
public final String getTypeString() {
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index f483752..0f94cbe 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -271,6 +271,16 @@
}
/**
+ * Returns the names of custom power components in order, so the first name in the array
+ * corresponds to the custom componentId
+ * {@link BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID}.
+ */
+ @NonNull
+ public String[] getCustomPowerComponentNames() {
+ return mCustomPowerComponentNames;
+ }
+
+ /**
* Returns an iterator for {@link android.os.BatteryStats.HistoryItem}'s.
*/
@NonNull
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index b069fb3..59db8f4 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -1235,10 +1235,11 @@
data.readCallingWorkSourceUid());
observer.callEnded(callSession, data.dataSize(), reply.dataSize(), workSourceUid);
}
+
+ checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
+ reply.recycle();
+ data.recycle();
}
- checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
- reply.recycle();
- data.recycle();
// Just in case -- we are done with the IPC, so there should be no more strict
// mode violations that have gathered for this thread. Either they have been
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 3d466a0..c646623 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -74,7 +74,7 @@
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
// Debuggable builds will throw an AssertionError if the number of map entries exceeds:
- private static final int CRASH_AT_SIZE = 20_000;
+ private static final int CRASH_AT_SIZE = 25_000;
/**
* We next warn when we exceed this bucket size.
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 2ea6329..9304e26 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -17,6 +17,7 @@
package android.os;
import android.app.Activity;
+import android.app.GameManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,8 +27,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -37,9 +36,6 @@
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
@@ -91,9 +87,6 @@
private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
- // ANGLE related properties.
- private static final String ANGLE_RULES_FILE = "a4a_rules.json";
- private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
"android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
@@ -124,6 +117,7 @@
private ClassLoader mClassLoader;
private String mLibrarySearchPaths;
private String mLibraryPermittedPaths;
+ private GameManager mGameManager;
private int mAngleOptInIndex = -1;
@@ -136,6 +130,8 @@
final ApplicationInfo appInfoWithMetaData =
getAppInfoWithMetadata(context, pm, packageName);
+ mGameManager = context.getSystemService(GameManager.class);
+
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -164,6 +160,23 @@
}
/**
+ * Query to determine if the Game Mode has enabled ANGLE.
+ */
+ private boolean isAngleEnabledByGameMode(Context context, String packageName) {
+ try {
+ final boolean gameModeEnabledAngle =
+ (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
+ Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
+ return gameModeEnabledAngle;
+ } catch (SecurityException e) {
+ Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled "
+ + "for package: " + packageName);
+ }
+
+ return false;
+ }
+
+ /**
* Query to determine if ANGLE should be used
*/
private boolean shouldUseAngle(Context context, Bundle coreSettings,
@@ -177,21 +190,16 @@
Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
+ "set to: '" + devOptIn + "'");
- // We only want to use ANGLE if the app is in the allowlist, or the developer has
- // explicitly chosen something other than default driver.
- // The allowlist will be generated by the ANGLE APK at both boot time and
- // ANGLE update time. It will only include apps mentioned in the rules file.
- final boolean allowed = checkAngleAllowlist(context, coreSettings, packageName);
+ // We only want to use ANGLE if the developer has explicitly chosen something other than
+ // default driver.
final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
-
- if (allowed) {
- Log.v(TAG, "ANGLE allowlist includes " + packageName);
- }
if (requested) {
Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
}
- return allowed || requested;
+ final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
+
+ return requested || gameModeEnabledAngle;
}
private int getVulkanVersion(PackageManager pm) {
@@ -488,117 +496,6 @@
}
/**
- * Attempt to setup ANGLE with a temporary rules file.
- * True: Temporary rules file was loaded.
- * False: Temporary rules file was *not* loaded.
- */
- private boolean setupAngleWithTempRulesFile(Context context,
- String packageName,
- String paths,
- String devOptIn) {
- /**
- * We only want to load a temp rules file for:
- * - apps that are marked 'debuggable' in their manifest
- * - devices that are running a userdebug build (ro.debuggable) or can inject libraries for
- * debugging (PR_SET_DUMPABLE).
- */
- if (!isDebuggable()) {
- Log.v(TAG, "Skipping loading temporary rules file");
- return false;
- }
-
- final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES);
-
- if (TextUtils.isEmpty(angleTempRules)) {
- Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty");
- return false;
- }
-
- Log.i(TAG, "Detected system property " + ANGLE_TEMP_RULES + ": " + angleTempRules);
-
- final File tempRulesFile = new File(angleTempRules);
- if (tempRulesFile.exists()) {
- Log.i(TAG, angleTempRules + " exists, loading file.");
- try {
- final FileInputStream stream = new FileInputStream(angleTempRules);
-
- try {
- final FileDescriptor rulesFd = stream.getFD();
- final long rulesOffset = 0;
- final long rulesLength = stream.getChannel().size();
- Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules);
-
- setAngleInfo(paths, packageName, devOptIn, null,
- rulesFd, rulesOffset, rulesLength);
-
- stream.close();
-
- // We successfully setup ANGLE, so return with good status
- return true;
- } catch (IOException e) {
- Log.w(TAG, "Hit IOException thrown by FileInputStream: " + e);
- }
- } catch (FileNotFoundException e) {
- Log.w(TAG, "Temp ANGLE rules file not found: " + e);
- } catch (SecurityException e) {
- Log.w(TAG, "Temp ANGLE rules file not accessible: " + e);
- }
- }
-
- return false;
- }
-
- /**
- * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
- * True: APK rules file was loaded.
- * False: APK rules file was *not* loaded.
- */
- private boolean setupAngleRulesApk(String anglePkgName,
- ApplicationInfo angleInfo,
- PackageManager pm,
- String packageName,
- String paths,
- String devOptIn,
- String[] features) {
- // Pass the rules file to loader for ANGLE decisions
- try {
- final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets();
-
- try {
- final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE);
-
- setAngleInfo(paths, packageName, devOptIn, features, assetsFd.getFileDescriptor(),
- assetsFd.getStartOffset(), assetsFd.getLength());
-
- assetsFd.close();
-
- return true;
- } catch (IOException e) {
- Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE
- + " from '" + anglePkgName + "': " + e);
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "': " + e);
- }
-
- return false;
- }
-
- /**
- * Pull ANGLE allowlist from GlobalSettings and compare against current package
- */
- private boolean checkAngleAllowlist(Context context, Bundle bundle, String packageName) {
- final ContentResolver contentResolver = context.getContentResolver();
- final List<String> angleAllowlist =
- getGlobalSettingsString(contentResolver, bundle,
- Settings.Global.ANGLE_ALLOWLIST);
-
- if (DEBUG) Log.v(TAG, "ANGLE allowlist: " + angleAllowlist);
-
- return angleAllowlist.contains(packageName);
- }
-
- /**
* Pass ANGLE details down to trigger enable logic
*
* @param context
@@ -660,28 +557,21 @@
if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths);
- // If the user has set the developer option to something other than default,
- // we need to call setupAngleRulesApk() with the package name and the developer
- // option value (native/angle/other). Then later when we are actually trying to
- // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
- // and can confidently answer yes/no based on the previously set developer
- // option value.
- final String devOptIn = getDriverForPackage(context, bundle, packageName);
-
- if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) {
- // We setup ANGLE with a temp rules file, so we're done here.
- return true;
+ // We need to call setAngleInfo() with the package name and the developer option value
+ //(native/angle/other). Then later when we are actually trying to load a driver,
+ //GraphicsEnv::getShouldUseAngle() has seen the package name before and can confidently
+ //answer yes/no based on the previously set developer option value.
+ final String devOptIn;
+ final String[] features = getAngleEglFeatures(context, bundle);
+ final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
+ if (gameModeEnabledAngle) {
+ devOptIn = ANGLE_GL_DRIVER_CHOICE_ANGLE;
+ } else {
+ devOptIn = getDriverForPackage(context, bundle, packageName);
}
+ setAngleInfo(paths, packageName, devOptIn, features);
- String[] features = getAngleEglFeatures(context, bundle);
-
- if (setupAngleRulesApk(
- anglePkgName, angleInfo, pm, packageName, paths, devOptIn, features)) {
- // ANGLE with rules is set up from the APK, hence return.
- return true;
- }
-
- return false;
+ return true;
}
/**
@@ -969,7 +859,7 @@
private static native void setGpuStats(String driverPackageName, String driverVersionName,
long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
private static native void setAngleInfo(String path, String appPackage, String devOptIn,
- String[] features, FileDescriptor rulesFd, long rulesOffset, long rulesLength);
+ String[] features);
private static native boolean getShouldUseAngle(String packageName);
private static native boolean setInjectLayersPrSetDumpable();
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 1e424d1..5d9f2189 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -10,9 +10,8 @@
# BatteryStats
per-file *BatteryConsumer* = file:/BATTERY_STATS_OWNERS
per-file BatteryManager* = file:/BATTERY_STATS_OWNERS
-per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
-per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
per-file PowerComponents.java = file:/BATTERY_STATS_OWNERS
+per-file *Stats* = file:/BATTERY_STATS_OWNERS
# Multiuser
per-file IUser* = file:/MULTIUSER_OWNERS
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 0c3debb1..26de711 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -389,7 +389,7 @@
mExecutor.execute(() -> {
boolean anyVibrating;
synchronized (mLock) {
- int allInitializedMask = 1 << mVibratorListeners.size() - 1;
+ int allInitializedMask = (1 << mVibratorListeners.size()) - 1;
int vibratorMask = 1 << vibratorIdx;
if ((mInitializedMask & vibratorMask) == 0) {
// First state report for this vibrator, set vibrating initial value.
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index ddb6533..d974e0c 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -131,6 +131,10 @@
private static native void nativeAsyncTraceBegin(long tag, String name, int cookie);
@FastNative
private static native void nativeAsyncTraceEnd(long tag, String name, int cookie);
+ @FastNative
+ private static native void nativeInstant(long tag, String name);
+ @FastNative
+ private static native void nativeInstantForTrack(long tag, String trackName, String name);
private Trace() {
}
@@ -258,6 +262,42 @@
}
/**
+ * Writes a trace message to indicate that a given section of code was invoked.
+ *
+ * @param traceTag The trace tag.
+ * @param methodName The method name to appear in the trace.
+ * @hide
+ */
+ public static void instant(long traceTag, String methodName) {
+ if (methodName == null) {
+ throw new IllegalArgumentException("methodName cannot be null");
+ }
+ if (isTagEnabled(traceTag)) {
+ nativeInstant(traceTag, methodName);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code was invoked.
+ *
+ * @param traceTag The trace tag.
+ * @param trackName The track where the event should appear in the trace.
+ * @param methodName The method name to appear in the trace.
+ * @hide
+ */
+ public static void instantForTrack(long traceTag, String trackName, String methodName) {
+ if (trackName == null) {
+ throw new IllegalArgumentException("trackName cannot be null");
+ }
+ if (methodName == null) {
+ throw new IllegalArgumentException("methodName cannot be null");
+ }
+ if (isTagEnabled(traceTag)) {
+ nativeInstantForTrack(traceTag, trackName, methodName);
+ }
+ }
+
+ /**
* Checks whether or not tracing is currently enabled. This is useful to avoid intermediate
* string creation for trace sections that require formatting. It is not necessary
* to guard all Trace method calls as they internally already check this. However it is
diff --git a/core/java/android/os/incremental/OWNERS b/core/java/android/os/incremental/OWNERS
index 47eee64..3634869 100644
--- a/core/java/android/os/incremental/OWNERS
+++ b/core/java/android/os/incremental/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 554432
alexbuy@google.com
schfan@google.com
-toddke@google.com
zyy@google.com
patb@google.com
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 54905ec..8928a42 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.IVold;
import java.util.List;
@@ -135,4 +136,19 @@
* {@link VolumeInfo#isPrimary()}
*/
public abstract List<String> getPrimaryVolumeIds();
+
+ /**
+ * Tells StorageManager that CE storage for this user has been prepared.
+ *
+ * @param userId userId for which CE storage has been prepared
+ */
+ public abstract void markCeStoragePrepared(@UserIdInt int userId);
+
+ /**
+ * Returns true when CE storage for this user has been prepared.
+ *
+ * When the user key is unlocked and CE storage has been prepared,
+ * it's ok to access and modify CE directories on volumes for this user.
+ */
+ public abstract boolean isCeStoragePrepared(@UserIdInt int userId);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 84be746..d0ef546 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2242,6 +2242,21 @@
public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
/**
+ * Activity Action: Show screen that lets user configure wifi tethering.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * Input: Nothing
+ * <p>
+ * Output: Nothing
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_WIFI_TETHER_SETTING =
+ "com.android.settings.WIFI_TETHER_SETTINGS";
+
+ /**
* Broadcast to trigger notification of asking user to enable MMS.
* Need to specify {@link #EXTRA_ENABLE_MMS_DATA_REQUEST_REASON} and {@link #EXTRA_SUB_ID}.
*
@@ -5741,7 +5756,6 @@
MOVED_TO_GLOBAL.add(Settings.Global.CONNECTIVITY_CHANGE_DELAY);
MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_SERVER);
- MOVED_TO_GLOBAL.add(Settings.Global.NSD_ON);
MOVED_TO_GLOBAL.add(Settings.Global.SET_INSTALL_LOCATION);
MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_INSTALL_LOCATION);
MOVED_TO_GLOBAL.add(Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY);
@@ -11861,6 +11875,12 @@
public static final String WIFI_MIGRATION_COMPLETED = "wifi_migration_completed";
/**
+ * Whether UWB should be enabled.
+ * @hide
+ */
+ public static final String UWB_ENABLED = "uwb_enabled";
+
+ /**
* Value to specify whether network quality scores and badging should be shown in the UI.
*
* Type: int (0 for false, 1 for true)
@@ -12610,13 +12630,6 @@
@Readable
public static final String MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS =
"min_duration_between_recovery_steps";
- /**
- * Whether network service discovery is enabled.
- *
- * @hide
- */
- @Readable
- public static final String NSD_ON = "nsd_on";
/**
* Let user pick default install location.
@@ -13686,13 +13699,6 @@
"angle_gl_driver_selection_values";
/**
- * List of package names that should check ANGLE rules
- * @hide
- */
- @Readable
- public static final String ANGLE_ALLOWLIST = "angle_allowlist";
-
- /**
* Lists of ANGLE EGL features for debugging.
* Each list of features is separated by a comma, each feature in each list is separated by
* a colon.
@@ -14931,6 +14937,16 @@
"power_button_long_press";
/**
+ * Override internal R.integer.config_longPressOnPowerDurationMs. It determines the length
+ * of power button press to be considered a long press in milliseconds.
+ * Used by PhoneWindowManager.
+ * @hide
+ */
+ @Readable
+ public static final String POWER_BUTTON_LONG_PRESS_DURATION_MS =
+ "power_button_long_press_duration_ms";
+
+ /**
* Overrides internal R.integer.config_veryLongPressOnPowerBehavior.
* Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
* Used by PhoneWindowManager.
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 0adc00e..1b26465 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3758,6 +3758,25 @@
public static final String NETWORK_TYPE_BITMASK = "network_type_bitmask";
/**
+ * Lingering radio technology (network type) bitmask.
+ * To check what values can be contained, refer to the NETWORK_TYPE_ constants in
+ * {@link android.telephony.TelephonyManager}.
+ * Bitmask for a radio tech R is (1 << (R - 1))
+ * <P>Type: INTEGER (long)</P>
+ * @hide
+ */
+ public static final String LINGERING_NETWORK_TYPE_BITMASK =
+ "lingering_network_type_bitmask";
+
+ /**
+ * Sets whether the PDU session brought up by this APN should always be on.
+ * See 3GPP TS 23.501 section 5.6.13
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String ALWAYS_ON = "always_on";
+
+ /**
* MVNO type:
* {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
* <P>Type: TEXT</P>
@@ -3835,11 +3854,31 @@
* connected, in bytes.
* <p>Type: INTEGER </p>
* @hide
+ * @deprecated use {@link #MTU_V4} or {@link #MTU_V6} instead
*/
@SystemApi
+ @Deprecated
public static final String MTU = "mtu";
/**
+ * The MTU (maximum transmit unit) size of the mobile interface for IPv4 to which the APN is
+ * connected, in bytes.
+ * <p>Type: INTEGER </p>
+ * @hide
+ */
+ @SystemApi
+ public static final String MTU_V4 = "mtu_v4";
+
+ /**
+ * The MTU (maximum transmit unit) size of the mobile interface for IPv6 to which the APN is
+ * connected, in bytes.
+ * <p>Type: INTEGER </p>
+ * @hide
+ */
+ @SystemApi
+ public static final String MTU_V6 = "mtu_v6";
+
+ /**
* APN edit status. APN could be added/edited/deleted by a user or carrier.
* see all possible returned APN edit status.
* <ul>
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 13274c6..29c7796 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -465,7 +465,7 @@
* <p>Prior to Android {@link android.os.Build.VERSION_CODES#P}, the metrics covered just the
* scenarios where the service knew how to autofill an activity, but Android
* {@link android.os.Build.VERSION_CODES#P} introduced a new mechanism called field classification,
- * which allows the service to dinamically classify the meaning of fields based on the existing user
+ * which allows the service to dynamically classify the meaning of fields based on the existing user
* data known by the service.
*
* <p>Typically, field classification can be used to detect fields that can be autofilled with
diff --git a/core/java/android/service/cloudsearch/OWNERS b/core/java/android/service/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/core/java/android/service/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 8e4a68e..4004148 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -436,7 +436,7 @@
try {
ApplicationInfo ai = context.getPackageManager()
.getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
- getUserId());
+ getNormalizedUserId());
mContext = context.createApplicationContext(ai,
Context.CONTEXT_RESTRICTED);
} catch (PackageManager.NameNotFoundException e) {
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
index e9dd2c3b..4cc732a 100644
--- a/core/java/android/service/translation/ITranslationService.aidl
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -24,7 +24,7 @@
/**
* System-wide on-device translation service.
*
- * <p>Services requests to translate text between different languages. The primary use case for this
+ * <p>Services requests to translate data between different languages. The primary use case for this
* service is automatic translation of text and web views, when the auto Translate feature is
* enabled.
*
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
index 93c006a..d454c39 100644
--- a/core/java/android/service/translation/TranslationService.java
+++ b/core/java/android/service/translation/TranslationService.java
@@ -48,6 +48,7 @@
import android.view.translation.TranslationRequest;
import android.view.translation.TranslationResponse;
import android.view.translation.TranslationSpec;
+import android.view.translation.Translator;
import com.android.internal.os.IResultReceiver;
@@ -81,7 +82,10 @@
* android.R.styleable#TranslationService translation-service}></code> tag.
*
* <p>Here's an example of how to use it on {@code AndroidManifest.xml}:
- * TODO: fill in doc example (check CCService/AFService).
+ * <pre> <translation-service
+ * android:settingsActivity="foo.bar.SettingsActivity"
+ * . . .
+ * /></pre>
*/
public static final String SERVICE_META_DATA = "android.translation_service";
@@ -148,7 +152,6 @@
void onTranslationSuccess(@NonNull TranslationResponse response);
/**
- * TODO: implement javadoc
* @removed use {@link #onTranslationSuccess} with an error response instead.
*/
@Deprecated
@@ -225,7 +228,7 @@
* should call back with {@code false}.</p>
*
* @param translationContext the {@link TranslationContext} of the session being created.
- * @param sessionId the int id of the session.
+ * @param sessionId the id of the session.
* @param callback {@link Consumer} to notify whether the session was successfully created.
*/
// TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
@@ -234,8 +237,6 @@
int sessionId, @NonNull Consumer<Boolean> callback);
/**
- * TODO: fill in javadoc.
- *
* @removed use {@link #onCreateTranslationSession(TranslationContext, int, Consumer)}
* instead.
*/
@@ -246,19 +247,16 @@
}
/**
- * TODO: fill in javadoc.
+ * Called when a translation session is finished.
*
- * @param sessionId
+ * <p>The translation session is finished when the client calls {@link Translator#destroy()} on
+ * the corresponding translator.
+ *
+ * @param sessionId id of the session that finished.
*/
public abstract void onFinishTranslationSession(int sessionId);
/**
- * TODO: fill in javadoc.
- *
- * @param request
- * @param sessionId
- * @param callback
- * @param cancellationSignal
* @removed use
* {@link #onTranslationRequest(TranslationRequest, int, CancellationSignal, Consumer)} instead.
*/
@@ -276,23 +274,29 @@
* {@link TranslationRequest#FLAG_PARTIAL_RESPONSES} was set, the service may call
* {@code callback.accept()} multiple times with partial responses.</p>
*
- * @param request
- * @param sessionId
- * @param callback
- * @param cancellationSignal
+ * @param request The translation request containing the data to be translated.
+ * @param sessionId id of the session that sent the translation request.
+ * @param cancellationSignal A {@link CancellationSignal} that notifies when a client has
+ * cancelled the operation in progress.
+ * @param callback {@link Consumer} to pass back the translation response.
*/
public abstract void onTranslationRequest(@NonNull TranslationRequest request, int sessionId,
@Nullable CancellationSignal cancellationSignal,
@NonNull Consumer<TranslationResponse> callback);
/**
- * TODO: fill in javadoc
+ * Called to request a set of {@link TranslationCapability}s that are supported by the service.
+ *
+ * <p>The set of translation capabilities are limited to those supporting the source and target
+ * {@link TranslationSpec.DataFormat}. e.g. Calling this with
+ * {@link TranslationSpec#DATA_FORMAT_TEXT} as source and target returns only capabilities that
+ * translates text to text.</p>
*
* <p>Must call {@code callback.accept} to pass back the set of translation capabilities.</p>
*
- * @param sourceFormat
- * @param targetFormat
- * @param callback
+ * @param sourceFormat data format restriction of the translation source spec.
+ * @param targetFormat data format restriction of the translation target spec.
+ * @param callback {@link Consumer} to pass back the set of translation capabilities.
*/
public abstract void onTranslationCapabilitiesRequest(
@TranslationSpec.DataFormat int sourceFormat,
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f477072..c9a0121 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -96,6 +96,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -152,6 +153,7 @@
private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private static final int MSG_ZOOM = 10100;
private static final int MSG_SCALE_PREVIEW = 10110;
+ private static final int MSG_REPORT_SHOWN = 10150;
private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
Float.NEGATIVE_INFINITY);
@@ -527,6 +529,39 @@
}
/**
+ * This will be called in the end of {@link #updateSurface(boolean, boolean, boolean)}.
+ * If true is returned, the engine will not report shown until rendering finished is
+ * reported. Otherwise, the engine will report shown immediately right after redraw phase
+ * in {@link #updateSurface(boolean, boolean, boolean)}.
+ *
+ * @hide
+ */
+ public boolean shouldWaitForEngineShown() {
+ return false;
+ }
+
+ /**
+ * Reports the rendering is finished, stops waiting, then invokes
+ * {@link IWallpaperEngineWrapper#reportShown()}.
+ *
+ * @hide
+ */
+ public void reportEngineShown(boolean waitForEngineShown) {
+ if (mIWallpaperEngine.mShownReported) return;
+ if (!waitForEngineShown) {
+ Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
+ mCaller.removeMessages(MSG_REPORT_SHOWN);
+ mCaller.sendMessage(message);
+ } else {
+ // if we are already waiting, no need to reset the timeout.
+ if (!mCaller.hasMessages(MSG_REPORT_SHOWN)) {
+ Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
+ mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5));
+ }
+ }
+ }
+
+ /**
* Control whether this wallpaper will receive raw touch events
* from the window manager as the user interacts with the window
* that is currently displaying the wallpaper. By default they
@@ -930,7 +965,7 @@
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
- Log.w(TAG, "Ignoring updateSurface: destroyed");
+ Log.w(TAG, "Ignoring updateSurface due to destroyed");
}
boolean fixedSize = false;
@@ -1197,7 +1232,6 @@
+ this);
onVisibilityChanged(false);
}
-
} finally {
mIsCreating = false;
mSurfaceCreated = true;
@@ -1207,7 +1241,7 @@
processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
reposition();
- mIWallpaperEngine.reportShown();
+ reportEngineShown(shouldWaitForEngineShown());
}
} catch (RemoteException ex) {
}
@@ -2048,6 +2082,8 @@
mShownReported = true;
try {
mConnection.engineShown(this);
+ Log.d(TAG, "Wallpaper has updated the surface:"
+ + mWallpaperManager.getWallpaperInfo());
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
return;
@@ -2201,6 +2237,9 @@
// Connection went away, nothing to do in here.
}
} break;
+ case MSG_REPORT_SHOWN: {
+ reportShown();
+ } break;
default :
Log.w(TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 362ea8c..5e647a4 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -115,7 +115,7 @@
@NonNull AttributionSource attributionSource) {
try {
if (mCurrentCallback == null) {
- boolean preflightPermissionCheckPassed = checkPermissionForPreflight(
+ boolean preflightPermissionCheckPassed = checkPermissionForPreflightNotHardDenied(
attributionSource);
if (preflightPermissionCheckPassed) {
if (DBG) {
@@ -470,10 +470,11 @@
return mStartedDataDelivery;
}
- private boolean checkPermissionForPreflight(AttributionSource attributionSource) {
- return PermissionChecker.checkPermissionForPreflight(RecognitionService.this,
- Manifest.permission.RECORD_AUDIO, attributionSource)
- == PermissionChecker.PERMISSION_GRANTED;
+ private boolean checkPermissionForPreflightNotHardDenied(AttributionSource attributionSource) {
+ int result = PermissionChecker.checkPermissionForPreflight(RecognitionService.this,
+ Manifest.permission.RECORD_AUDIO, attributionSource);
+ return result == PermissionChecker.PERMISSION_GRANTED
+ || result == PermissionChecker.PERMISSION_SOFT_DENIED;
}
void finishDataDelivery() {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index cb1cff9..9eaaa91 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -36,18 +36,24 @@
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
import android.telephony.Annotation.SrvccState;
+import android.telephony.TelephonyManager.CarrierPrivilegesListener;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.listeners.ListenerExecutor;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ITelephonyRegistry;
+import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.Executor;
/**
@@ -247,8 +253,8 @@
} else if (listener.mSubId != null) {
subId = listener.mSubId;
}
- sRegistry.listenWithEventList(
- subId, pkg, featureId, listener.callback, eventsList, notifyNow);
+ sRegistry.listenWithEventList(false, false, subId, pkg, featureId,
+ listener.callback, eventsList, notifyNow);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -263,11 +269,13 @@
* @param events List events
* @param notifyNow Whether to notify instantly
*/
- private void listenFromCallback(int subId, @NonNull String pkg, @NonNull String featureId,
+ private void listenFromCallback(boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess, int subId,
+ @NonNull String pkg, @NonNull String featureId,
@NonNull TelephonyCallback telephonyCallback, @NonNull int[] events,
boolean notifyNow) {
try {
- sRegistry.listenWithEventList(
+ sRegistry.listenWithEventList(renounceFineLocationAccess, renounceCoarseLocationAccess,
subId, pkg, featureId, telephonyCallback.callback, events, notifyNow);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -283,6 +291,8 @@
* UI. There is no timeout associated with showing this UX, so a carrier app must be sure to
* call with active set to false sometime after calling with it set to {@code true}.
* <p>
+ * This will apply to all subscriptions the carrier app has carrier privileges on.
+ * <p>
* Requires Permission: calling app has carrier privileges.
*
* @param active Whether the carrier network change is or shortly will be
@@ -298,6 +308,31 @@
}
/**
+ * Informs the system of an intentional upcoming carrier network change by a carrier app on the
+ * given {@code subscriptionId}. This call only used to allow the system to provide alternative
+ * UI while telephony is performing an action that may result in intentional, temporary network
+ * lack of connectivity.
+ * <p>
+ * Based on the active parameter passed in, this method will either show or hide the
+ * alternative UI. There is no timeout associated with showing this UX, so a carrier app must be
+ * sure to call with active set to false sometime after calling with it set to {@code true}.
+ * <p>
+ * Requires Permission: calling app has carrier privileges.
+ *
+ * @param subscriptionId the subscription of the carrier network.
+ * @param active whether the carrier network change is or shortly will be active. Set this value
+ * to true to begin showing alternative UI and false to stop.
+ * @see TelephonyManager#hasCarrierPrivileges
+ */
+ public void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
+ try {
+ sRegistry.notifyCarrierNetworkChangeWithSubId(subscriptionId, active);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
+ /**
* Notify call state changed on certain subscription.
*
* @param slotIndex for which call state changed. Can be derived from subId except when subId is
@@ -1161,14 +1196,17 @@
*
* @param callback The {@link TelephonyCallback} object to register.
*/
- public void registerTelephonyCallback(@NonNull @CallbackExecutor Executor executor,
+ public void registerTelephonyCallback(boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess,
+ @NonNull @CallbackExecutor Executor executor,
int subId, String pkgName, String attributionTag, @NonNull TelephonyCallback callback,
boolean notifyNow) {
if (callback == null) {
throw new IllegalStateException("telephony service is null.");
}
callback.init(executor);
- listenFromCallback(subId, pkgName, attributionTag, callback,
+ listenFromCallback(renounceFineLocationAccess, renounceCoarseLocationAccess, subId,
+ pkgName, attributionTag, callback,
getEventsFromCallback(callback).stream().mapToInt(i -> i).toArray(), notifyNow);
}
@@ -1179,6 +1217,120 @@
*/
public void unregisterTelephonyCallback(int subId, String pkgName, String attributionTag,
@NonNull TelephonyCallback callback, boolean notifyNow) {
- listenFromCallback(subId, pkgName, attributionTag, callback, new int[0], notifyNow);
+ listenFromCallback(false, false, subId,
+ pkgName, attributionTag, callback, new int[0], notifyNow);
+ }
+
+ private static class CarrierPrivilegesListenerWrapper extends ICarrierPrivilegesListener.Stub
+ implements ListenerExecutor {
+ private final WeakReference<CarrierPrivilegesListener> mListener;
+ private final Executor mExecutor;
+
+ CarrierPrivilegesListenerWrapper(CarrierPrivilegesListener listener, Executor executor) {
+ mListener = new WeakReference<>(listener);
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCarrierPrivilegesChanged(
+ List<String> privilegedPackageNames, int[] privilegedUids) {
+ Binder.withCleanCallingIdentity(
+ () ->
+ executeSafely(
+ mExecutor,
+ mListener::get,
+ cpl ->
+ cpl.onCarrierPrivilegesChanged(
+ privilegedPackageNames, privilegedUids)));
+ }
+ }
+
+ @GuardedBy("sCarrierPrivilegeListeners")
+ private static final WeakHashMap<
+ CarrierPrivilegesListener, WeakReference<CarrierPrivilegesListenerWrapper>>
+ sCarrierPrivilegeListeners = new WeakHashMap<>();
+
+ /**
+ * Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to
+ * receive callbacks when the set of packages with carrier privileges changes. The callback will
+ * immediately be called with the latest state.
+ *
+ * @param logicalSlotIndex The SIM slot to listen on
+ * @param executor The executor where {@code listener} will be invoked
+ * @param listener The callback to register
+ */
+ public void addCarrierPrivilegesListener(
+ int logicalSlotIndex,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CarrierPrivilegesListener listener) {
+ if (listener == null || executor == null) {
+ throw new IllegalArgumentException("listener and executor must be non-null");
+ }
+ synchronized (sCarrierPrivilegeListeners) {
+ WeakReference<CarrierPrivilegesListenerWrapper> existing =
+ sCarrierPrivilegeListeners.get(listener);
+ if (existing != null && existing.get() != null) {
+ Log.d(TAG, "addCarrierPrivilegesListener: listener already registered");
+ return;
+ }
+ CarrierPrivilegesListenerWrapper wrapper =
+ new CarrierPrivilegesListenerWrapper(listener, executor);
+ sCarrierPrivilegeListeners.put(listener, new WeakReference<>(wrapper));
+ try {
+ sRegistry.addCarrierPrivilegesListener(
+ logicalSlotIndex,
+ wrapper,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregisters a {@link CarrierPrivilegesListener}.
+ *
+ * @param listener The callback to unregister
+ */
+ public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must be non-null");
+ }
+ synchronized (sCarrierPrivilegeListeners) {
+ WeakReference<CarrierPrivilegesListenerWrapper> ref =
+ sCarrierPrivilegeListeners.remove(listener);
+ if (ref == null) return;
+ CarrierPrivilegesListenerWrapper wrapper = ref.get();
+ if (wrapper == null) return;
+ try {
+ sRegistry.removeCarrierPrivilegesListener(wrapper, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Notify listeners that the set of packages with carrier privileges has changed.
+ *
+ * @param logicalSlotIndex The SIM slot the change occurred on
+ * @param privilegedPackageNames The updated set of packages names with carrier privileges
+ * @param privilegedUids The updated set of UIDs with carrier privileges
+ */
+ public void notifyCarrierPrivilegesChanged(
+ int logicalSlotIndex,
+ @NonNull List<String> privilegedPackageNames,
+ @NonNull int[] privilegedUids) {
+ if (privilegedPackageNames == null || privilegedUids == null) {
+ throw new IllegalArgumentException(
+ "privilegedPackageNames and privilegedUids must be non-null");
+ }
+ try {
+ sRegistry.notifyCarrierPrivilegesChanged(
+ logicalSlotIndex, privilegedPackageNames, privilegedUids);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 1f11d10..1a7ec7f 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -782,7 +782,7 @@
int spanStart = runStart;
int spanLimit;
- if (mSpanned == null) {
+ if (mSpanned == null || runStart == runLimit) {
spanLimit = runLimit;
} else {
int target = after ? offset + 1 : offset;
diff --git a/core/java/android/text/method/TranslationTransformationMethod.java b/core/java/android/text/method/TranslationTransformationMethod.java
index 80387aa..43d186e 100644
--- a/core/java/android/text/method/TranslationTransformationMethod.java
+++ b/core/java/android/text/method/TranslationTransformationMethod.java
@@ -62,6 +62,13 @@
return mOriginalTranslationMethod;
}
+ /**
+ * Returns the {@link TextView}'s {@link ViewTranslationResponse}.
+ */
+ public ViewTranslationResponse getViewTranslationResponse() {
+ return mTranslationResponse;
+ }
+
@Override
public CharSequence getTransformation(CharSequence source, View view) {
if (!mAllowLengthChanges) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6c3c383..546d6de 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -51,6 +51,10 @@
/** @hide */
public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub";
+ /** @hide */
+ public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
+ "settings_enable_monitor_phantom_procs";
+
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -72,12 +76,14 @@
DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "true");
DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
+ DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
}
private static final Set<String> PERSISTENT_FLAGS;
static {
PERSISTENT_FLAGS = new HashSet<>();
PERSISTENT_FLAGS.add(SETTINGS_PROVIDER_MODEL);
+ PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
}
/**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8021636..a7ecf1f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -814,9 +814,10 @@
* @param displayId The display associated with the window context
* @param options A bundle used to pass window-related options and choose the right DisplayArea
*
- * @return {@code true} if the WindowContext is attached to the DisplayArea successfully.
+ * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is
+ * attached to the DisplayArea successfully. {@code null}, otherwise.
*/
- boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
+ Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
in Bundle options);
/**
diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java
index 8808827..758f9ab 100644
--- a/core/java/android/view/ScrollCaptureResponse.java
+++ b/core/java/android/view/ScrollCaptureResponse.java
@@ -53,6 +53,10 @@
@Nullable
private String mWindowTitle = null;
+ /** The package which owns the window. */
+ @Nullable
+ private String mPackageName = null;
+
/** Carries additional logging and debugging information when enabled. */
@NonNull
@DataClass.PluralOf("message")
@@ -77,7 +81,7 @@
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -97,6 +101,7 @@
@Nullable Rect windowBounds,
@Nullable Rect boundsInWindow,
@Nullable String windowTitle,
+ @Nullable String packageName,
@NonNull ArrayList<String> messages) {
this.mDescription = description;
com.android.internal.util.AnnotationValidations.validate(
@@ -105,6 +110,7 @@
this.mWindowBounds = windowBounds;
this.mBoundsInWindow = boundsInWindow;
this.mWindowTitle = windowTitle;
+ this.mPackageName = packageName;
this.mMessages = messages;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mMessages);
@@ -153,6 +159,14 @@
}
/**
+ * The package name of the process the window is owned by.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* Carries additional logging and debugging information when enabled.
*/
@DataClass.Generated.Member
@@ -172,6 +186,7 @@
"windowBounds = " + mWindowBounds + ", " +
"boundsInWindow = " + mBoundsInWindow + ", " +
"windowTitle = " + mWindowTitle + ", " +
+ "packageName = " + mPackageName + ", " +
"messages = " + mMessages +
" }";
}
@@ -187,12 +202,14 @@
if (mWindowBounds != null) flg |= 0x4;
if (mBoundsInWindow != null) flg |= 0x8;
if (mWindowTitle != null) flg |= 0x10;
+ if (mPackageName != null) flg |= 0x20;
dest.writeByte(flg);
dest.writeString(mDescription);
if (mConnection != null) dest.writeStrongInterface(mConnection);
if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags);
if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags);
if (mWindowTitle != null) dest.writeString(mWindowTitle);
+ if (mPackageName != null) dest.writeString(mPackageName);
dest.writeStringList(mMessages);
}
@@ -213,6 +230,7 @@
Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
String windowTitle = (flg & 0x10) == 0 ? null : in.readString();
+ String packageName = (flg & 0x20) == 0 ? null : in.readString();
ArrayList<String> messages = new ArrayList<>();
in.readStringList(messages);
@@ -223,6 +241,7 @@
this.mWindowBounds = windowBounds;
this.mBoundsInWindow = boundsInWindow;
this.mWindowTitle = windowTitle;
+ this.mPackageName = packageName;
this.mMessages = messages;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mMessages);
@@ -256,6 +275,7 @@
private @Nullable Rect mWindowBounds;
private @Nullable Rect mBoundsInWindow;
private @Nullable String mWindowTitle;
+ private @Nullable String mPackageName;
private @NonNull ArrayList<String> mMessages;
private long mBuilderFieldsSet = 0L;
@@ -319,12 +339,23 @@
}
/**
+ * The package name of the process the window is owned by.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mPackageName = value;
+ return this;
+ }
+
+ /**
* Carries additional logging and debugging information when enabled.
*/
@DataClass.Generated.Member
public @NonNull Builder setMessages(@NonNull ArrayList<String> value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20;
+ mBuilderFieldsSet |= 0x40;
mMessages = value;
return this;
}
@@ -340,7 +371,7 @@
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ScrollCaptureResponse build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x40; // Mark builder used
+ mBuilderFieldsSet |= 0x80; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mDescription = "";
@@ -358,6 +389,9 @@
mWindowTitle = null;
}
if ((mBuilderFieldsSet & 0x20) == 0) {
+ mPackageName = null;
+ }
+ if ((mBuilderFieldsSet & 0x40) == 0) {
mMessages = new ArrayList<>();
}
ScrollCaptureResponse o = new ScrollCaptureResponse(
@@ -366,12 +400,13 @@
mWindowBounds,
mBoundsInWindow,
mWindowTitle,
+ mPackageName,
mMessages);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x40) != 0) {
+ if ((mBuilderFieldsSet & 0x80) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -379,10 +414,10 @@
}
@DataClass.Generated(
- time = 1614833185795L,
- codegenVersion = "1.0.22",
+ time = 1628630366187L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ff2d2eb..fa7330f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -98,6 +98,7 @@
private static native int nativeSetFrameRate(
long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
+ private static native void nativeDestroy(long nativeObject);
public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@@ -339,6 +340,9 @@
*/
@UnsupportedAppUsage
public void destroy() {
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ }
release();
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index c1956e4..4b8b607de 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -903,7 +903,7 @@
mSurfaceAlpha = 1f;
synchronized (mSurfaceControlLock) {
- mSurface.release();
+ mSurface.destroy();
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f4223fb..2320434 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -20830,13 +20830,14 @@
mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
}
+ notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
+
mAttachInfo = null;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchDetachedFromWindow();
}
notifyEnterOrExitForAutoFillIfNeeded(false);
- notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
}
/**
@@ -26841,44 +26842,38 @@
* Handles drag events sent by the system following a call to
* {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
* startDragAndDrop()}.
- *<p>
- * When the system calls this method, it passes a
- * {@link android.view.DragEvent} object. A call to
- * {@link android.view.DragEvent#getAction()} returns one of the action type constants defined
- * in DragEvent. The method uses these to determine what is happening in the drag and drop
- * operation.
- * </p>
* <p>
- * The default implementation returns false, except if an {@link OnReceiveContentListener}
- * is {@link #setOnReceiveContentListener set} for this view. If an
- * {@link OnReceiveContentListener} is set, the default implementation...
+ * The system calls this method and passes a {@link DragEvent} object in response to drag and
+ * drop events. This method can then call {@link DragEvent#getAction()} to determine the state
+ * of the drag and drop operation.
+ * <p>
+ * The default implementation returns {@code false} unless an {@link OnReceiveContentListener}
+ * has been set for this view (see {@link #setOnReceiveContentListener}), in which case
+ * the default implementation does the following:
* <ul>
- * <li>returns true for an
- * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event
- * <li>calls {@link #performReceiveContent} for an
- * {@link android.view.DragEvent#ACTION_DROP ACTION_DROP} event
- * <li>returns true for an {@link android.view.DragEvent#ACTION_DROP ACTION_DROP} event, if
- * the listener consumed some or all of the content
+ * <li>Returns {@code true} for an
+ * {@link DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event
+ * <li>Calls {@link #performReceiveContent} for an
+ * {@link DragEvent#ACTION_DROP ACTION_DROP} event
+ * <li>Returns {@code true} for an {@link DragEvent#ACTION_DROP ACTION_DROP} event if the
+ * {@code OnReceiveContentListener} consumed some or all of the content
* </ul>
- * </p>
*
- * @param event The {@link android.view.DragEvent} sent by the system.
- * The {@link android.view.DragEvent#getAction()} method returns an action type constant defined
- * in DragEvent, indicating the type of drag event represented by this object.
- * @return {@code true} if the method was successful, otherwise {@code false}.
- * <p>
- * The method should return {@code true} in response to an action type of
- * {@link android.view.DragEvent#ACTION_DRAG_STARTED} to receive drag events for the current
- * operation.
- * </p>
- * <p>
- * The method should also return {@code true} in response to an action type of
- * {@link android.view.DragEvent#ACTION_DROP} if it consumed the drop, or
- * {@code false} if it didn't.
- * </p>
- * <p>
- * For all other events, the return value is ignored.
- * </p>
+ * @param event The {@link DragEvent} object sent by the system. The
+ * {@link DragEvent#getAction()} method returns an action type constant that indicates the
+ * type of drag event represented by this object.
+ * @return {@code true} if the method successfully handled the drag event, otherwise
+ * {@code false}.
+ * <p>
+ * The method must return {@code true} in response to an
+ * {@link DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} action type to continue to
+ * receive drag events for the current drag and drop operation.
+ * <p>
+ * The method should return {@code true} in response to an
+ * {@link DragEvent#ACTION_DROP ACTION_DROP} action type if the dropped data was consumed
+ * (at least partially); {@code false}, if none of the data was consumed.
+ * <p>
+ * For all other events, the return value is {@code false}.
*/
public boolean onDragEvent(DragEvent event) {
if (mListenerInfo == null || mListenerInfo.mOnReceiveContentListener == null) {
@@ -28843,27 +28838,27 @@
}
/**
- * Interface definition for a callback to be invoked when a drag is being dispatched
- * to this view. The callback will be invoked before the hosting view's own
- * onDrag(event) method. If the listener wants to fall back to the hosting view's
- * onDrag(event) behavior, it should return 'false' from this callback.
+ * Interface definition for a listener that's invoked when a drag event is dispatched to this
+ * view. The listener is invoked before the view's own
+ * {@link #onDragEvent(DragEvent)} method. To fall back to the view's
+ * {@code onDragEvent(DragEvent)} behavior, return {@code false} from the listener method.
*
* <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For a guide to implementing drag and drop features, read the
- * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
+ * <h3>Developer Guides</h3>
+ * <p>For a guide to implementing drag and drop features, see the
+ * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and drop</a> developer guide.</p>
* </div>
*/
public interface OnDragListener {
/**
- * Called when a drag event is dispatched to a view. This allows listeners
- * to get a chance to override base View behavior.
+ * Called when a drag event is dispatched to a view. Enables listeners to override the
+ * base behavior provided by {@link #onDragEvent(DragEvent)}.
*
- * @param v The View that received the drag event.
- * @param event The {@link android.view.DragEvent} object for the drag event.
- * @return {@code true} if the drag event was handled successfully, or {@code false}
- * if the drag event was not handled. Note that {@code false} will trigger the View
- * to call its {@link #onDragEvent(DragEvent) onDragEvent()} handler.
+ * @param v The {@code View} that received the drag event.
+ * @param event The event object for the drag event.
+ * @return {@code true} if the drag event was handled successfully; {@code false}, if the
+ * drag event was not handled. <b>Note:</b> A {@code false} return value triggers the
+ * view's {@link #onDragEvent(DragEvent)} handler.
*/
boolean onDrag(View v, DragEvent event);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2257f6c..2a82d99 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2836,8 +2836,13 @@
}
}
+ final boolean surfaceControlChanged =
+ (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
+ == RELAYOUT_RES_SURFACE_CHANGED;
+
if (mSurfaceControl.isValid()) {
- updateOpacity(mWindowAttributes, dragResizing);
+ updateOpacity(mWindowAttributes, dragResizing,
+ surfaceControlChanged /*forceUpdate */);
}
if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
@@ -2872,9 +2877,7 @@
// RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new
// SurfaceControl.
surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
- || (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
- == RELAYOUT_RES_SURFACE_CHANGED)
- && mSurface.isValid();
+ || surfaceControlChanged) && mSurface.isValid();
if (surfaceReplaced) {
mSurfaceSequenceId++;
}
@@ -7824,7 +7827,8 @@
return relayoutResult;
}
- private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing) {
+ private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing,
+ boolean forceUpdate) {
boolean opaque = false;
if (!PixelFormat.formatHasAlpha(params.format)
@@ -7840,7 +7844,7 @@
opaque = true;
}
- if (mIsSurfaceOpaque == opaque) {
+ if (!forceUpdate && mIsSurfaceOpaque == opaque) {
return;
}
@@ -9493,6 +9497,7 @@
ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder();
response.setWindowTitle(getTitle().toString());
+ response.setPackageName(mContext.getPackageName());
StringWriter writer = new StringWriter();
IndentingPrintWriter pw = new IndentingPrintWriter(writer);
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
index ec55a02..3365c70 100644
--- a/core/java/android/view/animation/TranslateAnimation.java
+++ b/core/java/android/view/animation/TranslateAnimation.java
@@ -57,6 +57,9 @@
/** @hide */
protected float mToYDelta;
+ private int mWidth;
+ private int mParentWidth;
+
/**
* Constructor used when a TranslateAnimation is loaded from a resource.
*
@@ -179,5 +182,60 @@
mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
+
+ mWidth = width;
+ mParentWidth = parentWidth;
+ }
+
+ /**
+ * Checks whether or not the translation is exclusively an x axis translation.
+ *
+ * @hide
+ */
+ public boolean isXAxisTransition() {
+ return mFromXDelta - mToXDelta != 0 && mFromYDelta - mToYDelta == 0;
+ }
+
+ /**
+ * Checks whether or not the translation is a full width x axis slide in or out translation.
+ *
+ * @hide
+ */
+ public boolean isFullWidthTranslate() {
+ boolean isXAxisSlideTransition =
+ isSlideInLeft() || isSlideOutRight() || isSlideInRight() || isSlideOutLeft();
+ return mWidth == mParentWidth && isXAxisSlideTransition;
+ }
+
+ private boolean isSlideInLeft() {
+ boolean startsOutOfParentOnLeft = mFromXDelta <= -mWidth;
+ return startsOutOfParentOnLeft && endsXEnclosedWithinParent();
+ }
+
+ private boolean isSlideOutRight() {
+ boolean endOutOfParentOnRight = mToXDelta >= mParentWidth;
+ return startsXEnclosedWithinParent() && endOutOfParentOnRight;
+ }
+
+ private boolean isSlideInRight() {
+ boolean startsOutOfParentOnRight = mFromXDelta >= mParentWidth;
+ return startsOutOfParentOnRight && endsXEnclosedWithinParent();
+ }
+
+ private boolean isSlideOutLeft() {
+ boolean endOutOfParentOnLeft = mToXDelta <= -mWidth;
+ return startsXEnclosedWithinParent() && endOutOfParentOnLeft;
+ }
+
+ private boolean endsXEnclosedWithinParent() {
+ return mWidth <= mParentWidth
+ && mToXDelta + mWidth <= mParentWidth
+ && mToXDelta >= 0;
+ }
+
+ private boolean startsXEnclosedWithinParent() {
+ return mWidth <= mParentWidth
+ && mFromXDelta + mWidth <= mParentWidth
+ && mFromXDelta >= 0;
}
}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 5f036a3..4d7d182 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -770,20 +770,25 @@
boolean beginBatchEdit();
/**
- * Tell the editor that you are done with a batch edit previously
- * initiated with {@link #beginBatchEdit}. This ends the latest
- * batch only.
+ * Tell the editor that you are done with a batch edit previously initiated with
+ * {@link #beginBatchEdit()}. This ends the latest batch only.
*
- * <p><strong>IME authors:</strong> make sure you call this
- * exactly once for each call to {@link #beginBatchEdit}.</p>
+ * <p><strong>IME authors:</strong> make sure you call this exactly once for each call to
+ * {@link #beginBatchEdit()}.</p>
*
- * <p><strong>Editor authors:</strong> please be careful about
- * batch edit nesting. Updates still to be held back until the end
- * of the last batch edit.</p>
+ * <p><strong>Editor authors:</strong> please be careful about batch edit nesting. Updates still
+ * to be held back until the end of the last batch edit. In case you are delegating this API
+ * call to the one obtained from
+ * {@link android.widget.EditText#onCreateInputConnection(EditorInfo)}, there was an off-by-one
+ * that had returned {@code true} when its nested batch edit count becomes {@code 0} as a result
+ * of invoking this API. This bug is fixed in {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ * </p>
*
- * @return true if there is still a batch edit in progress after closing
- * the latest one (in other words, if the nesting count is > 0), false
- * otherwise or if the input connection is no longer valid.
+ * @return For editor authors, you must return {@code true} if a batch edit is still in progress
+ * after closing the latest one (in other words, if the nesting count is still a
+ * positive number). Return {@code false} otherwise. For IME authors, you will
+ * always receive {@code true} as long as the request was sent to the editor, and
+ * receive {@code false} only if the input connection is no longer valid.
*/
boolean endBatchEdit();
diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java
index 65b749a..b7e13dd 100644
--- a/core/java/android/view/translation/TranslationCapability.java
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -40,15 +40,18 @@
public final class TranslationCapability implements Parcelable {
/**
- * TODO: fill in javadoc
+ * The translation service supports translation between the source and target specs, and it is
+ * ready to be downloaded onto the device.
*/
public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1;
/**
- * TODO: fill in javadoc
+ * The translation service supports translation between the source and target specs, and it is
+ * being downloaded onto the device currently.
*/
public static final @ModelState int STATE_DOWNLOADING = 2;
/**
- * TODO: fill in javadoc
+ * The translation service supports translation between the source and target specs, and it is
+ * downloaded and ready to use on device.
*/
public static final @ModelState int STATE_ON_DEVICE = 3;
/**
@@ -305,7 +308,7 @@
};
@DataClass.Generated(
- time = 1624307114468L,
+ time = 1629158466039L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java",
inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_NOT_AVAILABLE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_REMOVED_AND_AVAILABLE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)")
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
index df4836e..0d41851 100644
--- a/core/java/android/view/translation/TranslationRequest.java
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -39,12 +39,16 @@
public static final @RequestFlags int FLAG_TRANSLATION_RESULT = 0x1;
/**
* Indicates this request wants to receive the dictionary result.
- * TODO: describe the structure of the result.
+ *
+ * <p>See {@link TranslationResponseValue#EXTRA_DEFINITIONS} for more detail on the structure
+ * of the returned data.
*/
public static final @RequestFlags int FLAG_DICTIONARY_RESULT = 0x2;
/**
* Indicates this request wants to receive the transliteration result.
- * TODO: describe the structure of the result.
+ *
+ * <p>This returns a CharSequence representation of the transliteration of the translated text.
+ * See {@link TranslationResponseValue#getTransliteration()}.
*/
public static final @RequestFlags int FLAG_TRANSLITERATION_RESULT = 0x4;
/**
@@ -327,7 +331,8 @@
return this;
}
- /** @see #setTranslationRequestValues
+ /**
+ * @see #setTranslationRequestValues
* @removed
*/
@DataClass.Generated.Member
@@ -352,7 +357,8 @@
return this;
}
- /** @see #setViewTranslationRequests
+ /**
+ * @see #setViewTranslationRequests
* @removed
*/
@DataClass.Generated.Member
@@ -394,7 +400,7 @@
}
@DataClass.Generated(
- time = 1620429997487L,
+ time = 1629159107226L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/translation/TranslationRequest.java",
inputSignatures = "public static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_DICTIONARY_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLITERATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_PARTIAL_RESPONSES\nprivate final @android.view.translation.TranslationRequest.RequestFlags int mFlags\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.List<android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"viewTranslationRequest\") java.util.List<android.view.translation.ViewTranslationRequest> mViewTranslationRequests\nprivate static int defaultFlags()\nprivate static java.util.List<android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nprivate static java.util.List<android.view.translation.ViewTranslationRequest> defaultViewTranslationRequests()\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genBuilder=true)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/view/translation/TranslationResponseValue.java b/core/java/android/view/translation/TranslationResponseValue.java
index a24dbc3..9dff2d5 100644
--- a/core/java/android/view/translation/TranslationResponseValue.java
+++ b/core/java/android/view/translation/TranslationResponseValue.java
@@ -93,9 +93,11 @@
@NonNull
private final Bundle mExtras;
+ // TODO: Add example of transliteration.
/**
* The transliteration result of the translated text.
- * TODO: Describe the result structure.
+ *
+ * <p>This returns a CharSequence representation of the transliteration of the translated text.
*/
@Nullable
private final CharSequence mTransliteration;
@@ -223,7 +225,8 @@
/**
* The transliteration result of the translated text.
- * TODO: Describe the result structure.
+ *
+ * <p>This returns a CharSequence representation of the transliteration of the translated text.
*/
@DataClass.Generated.Member
public @Nullable CharSequence getTransliteration() {
@@ -407,7 +410,8 @@
/**
* The transliteration result of the translated text.
- * TODO: Describe the result structure.
+ *
+ * <p>This returns a CharSequence representation of the transliteration of the translated text.
*/
@DataClass.Generated.Member
public @NonNull Builder setTransliteration(@NonNull CharSequence value) {
@@ -448,7 +452,7 @@
}
@DataClass.Generated(
- time = 1622133051937L,
+ time = 1631057245846L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/translation/TranslationResponseValue.java",
inputSignatures = "public static final int STATUS_SUCCESS\npublic static final int STATUS_ERROR\npublic static final java.lang.String EXTRA_DEFINITIONS\nprivate final @android.view.translation.TranslationResponseValue.Status int mStatusCode\nprivate final @android.annotation.Nullable java.lang.CharSequence mText\nprivate final @android.annotation.NonNull android.os.Bundle mExtras\nprivate final @android.annotation.Nullable java.lang.CharSequence mTransliteration\npublic static @android.annotation.NonNull android.view.translation.TranslationResponseValue forError()\nprivate static java.lang.CharSequence defaultText()\nprivate static android.os.Bundle defaultExtras()\nprivate boolean extrasEquals(android.os.Bundle)\nprivate static java.lang.CharSequence defaultTransliteration()\nclass TranslationResponseValue extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index a833591..d078c2c 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -356,7 +356,11 @@
}
for (int i = 0; i < translatedResult.size(); i++) {
final AutofillId autofillId = new AutofillId(translatedResult.keyAt(i));
- final View view = mViews.get(autofillId).get();
+ final WeakReference<View> viewRef = mViews.get(autofillId);
+ if (viewRef == null) {
+ continue;
+ }
+ final View view = viewRef.get();
if (view == null) {
Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId
+ " may be gone.");
@@ -416,22 +420,30 @@
Log.w(TAG, "No AutofillId is set in ViewTranslationResponse");
continue;
}
- final View view = mViews.get(autofillId).get();
+ final WeakReference<View> viewRef = mViews.get(autofillId);
+ if (viewRef == null) {
+ continue;
+ }
+ final View view = viewRef.get();
if (view == null) {
Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId
+ " may be gone.");
continue;
}
mActivity.runOnUiThread(() -> {
+ ViewTranslationCallback callback = view.getViewTranslationCallback();
if (view.getViewTranslationResponse() != null
&& view.getViewTranslationResponse().equals(response)) {
- if (DEBUG) {
- Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
- + ". Ignoring.");
+ if (callback instanceof TextViewTranslationCallback) {
+ if (((TextViewTranslationCallback) callback).isShowingTranslation()) {
+ if (DEBUG) {
+ Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
+ + ". Ignoring.");
+ }
+ return;
+ }
}
- return;
}
- ViewTranslationCallback callback = view.getViewTranslationCallback();
if (callback == null) {
if (view instanceof TextView) {
// developer doesn't provide their override, we set the default TextView
@@ -590,9 +602,8 @@
final View rootView = roots.get(rootNum).getView();
if (rootView instanceof ViewGroup) {
findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds);
- } else {
- addViewIfNeeded(sourceViewIds, rootView);
}
+ addViewIfNeeded(sourceViewIds, rootView);
}
}
@@ -603,9 +614,8 @@
final View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup) {
findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds);
- } else {
- addViewIfNeeded(sourceViewIds, child);
}
+ addViewIfNeeded(sourceViewIds, child);
}
}
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index b9ed32c..3012e93 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -33,6 +33,7 @@
import android.util.Log;
import android.view.View;
import android.view.autofill.AutofillId;
+import android.widget.TextView;
import com.android.internal.annotations.GuardedBy;
@@ -42,11 +43,50 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
-// TODO(b/178044703): Describe what UI Translation is.
/**
- * The {@link UiTranslationManager} class provides ways for apps to use the ui translation
+ * <p>The {@link UiTranslationManager} class provides ways for apps to use the ui translation
* function in framework.
+ *
+ * <p> The UI translation provides ways for apps to support inline translation for the views. For
+ * example the system supports text translation for {@link TextView}. To support UI translation for
+ * your views, you should override the following methods to provide the content to be translated
+ * and deal with the translated result. Here is an example for {@link TextView}-like views:
+ *
+ * <pre><code>
+ * public class MyTextView extends View {
+ * public MyTextView(...) {
+ * // implements how to show the translated result in your View in
+ * // ViewTranslationCallback and set it by setViewTranslationCallback()
+ * setViewTranslationCallback(new MyViewTranslationCallback());
+ * }
+ *
+ * public void onCreateViewTranslationRequest(int[] supportedFormats,
+ * Consumer<ViewTranslationRequest> requestsCollector) {
+ * // collect the information that needs to be translated
+ * ViewTranslationRequest.Builder requestBuilder =
+ * new ViewTranslationRequest.Builder(getAutofillId());
+ * requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
+ * TranslationRequestValue.forText(etText()));
+ * requestsCollector.accept(requestBuilder.build());
+ * }
+ *
+ * public void onProvideContentCaptureStructure(
+ * ViewStructure structure, int flags) {
+ * // set ViewTranslationResponse
+ * super.onViewTranslationResponse(response);
+ * }
+ * }
+ * </code></pre>
+ *
+ * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws the
+ * HTML using {@link android.graphics.Canvas} or native libraries in a different render process),
+ * you must override {@link View#onCreateVirtualViewTranslationRequests(long[], int[], Consumer)} to
+ * provide the content to be translated and implement
+ * {@link View#onVirtualViewTranslationResponses(android.util.LongSparseArray)} for the translated
+ * result. You also need to implement {@link android.view.translation.ViewTranslationCallback} to
+ * handle the translated information show or hide in your {@link View}.
*/
public final class UiTranslationManager {
@@ -248,14 +288,14 @@
}
}
- // TODO(b/178044703): Fix the View API link when it becomes public.
/**
* Register for notifications of UI Translation state changes on the foreground activity. This
* is available to the owning application itself and also the current input method.
* <p>
* The application whose UI is being translated can use this to customize the UI Translation
* behavior in ways that aren't made easy by methods like
- * View#onCreateTranslationRequest().
+ * {@link View#onCreateViewTranslationRequest(int[], Consumer)}.
+ *
* <p>
* Input methods can use this to offer complementary features to UI Translation; for example,
* enabling outgoing message translation when the system is translating incoming messages in a
diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java
index 6efd621..66c028b 100644
--- a/core/java/android/view/translation/ViewTranslationCallback.java
+++ b/core/java/android/view/translation/ViewTranslationCallback.java
@@ -19,9 +19,17 @@
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.view.View;
+import android.view.contentcapture.ContentCaptureSession;
/**
- * Callback for handling the translated information show or hide in the {@link View}.
+ * <p> Callback for handling the translated information show or hide in the {@link View}.
+ *
+ * <p> When the platform intelligence starts translation of an app's ui, the system will call
+ * {@link View#dispatchCreateViewTranslationRequest} to collect the {@link ViewTranslationRequest}s
+ * for translation purpose by traversing the hierarchy then send to translation service. After
+ * receiving the {@link ViewTranslationResponse}, the system will call
+ * {@link ViewTranslationCallback#onShowTranslation(View)} to show the translated information for
+ * the {@link View}.
*/
@UiThread
public interface ViewTranslationCallback {
@@ -33,13 +41,24 @@
* method will not be called before {@link View#onViewTranslationResponse} or
* {@link View#onVirtualViewTranslationResponses}.
*
+ * <p> NOTE: It is possible the user changes text that causes a new
+ * {@link ViewTranslationResponse} returns to show the new translation. If you cache the
+ * {@link ViewTranslationResponse} here, you should remember to keep the cached value up
+ * to date.
+ *
+ * <p> NOTE: For TextView implementation, {@link ContentCaptureSession#notifyViewTextChanged}
+ * shouldn't be called with the translated text, simply calling setText() here will trigger the
+ * method. You should either override {@code View#onProvideContentCaptureStructure()} to report
+ * the original text instead of the translated text or use a different approach to display the
+ * translated text.
+ *
* See {@link View#onViewTranslationResponse} for how to get the translated information.
*
* @return {@code true} if the View handles showing the translation.
*/
boolean onShowTranslation(@NonNull View view);
/**
- * Called when the user wants to show the original text instead of the translated text. This
+ * Called when user wants to view the original content instead of the translated content. This
* method will not be called before {@link View#onViewTranslationResponse} or
* {@link View#onViewTranslationResponse}.
*
@@ -47,7 +66,8 @@
*/
boolean onHideTranslation(@NonNull View view);
/**
- * Called when the user finish the Ui translation and no longer to show the translated text.
+ * Called when the translation state is no longer needed. It should restore the original content
+ * and clear all saved states.
*
* @return {@code true} if the View handles clearing the translation.
*/
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 91fc5a5..fe5eb08 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -740,6 +740,16 @@
}
@Override
+ public UserHandle getUser() {
+ return mContextForResources.getUser();
+ }
+
+ @Override
+ public int getUserId() {
+ return mContextForResources.getUserId();
+ }
+
+ @Override
public boolean isRestricted() {
// Override isRestricted and direct to resource's implementation. The isRestricted is
// used for determining the risky resources loading, e.g. fonts, thus direct to context
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 9d60009..4a78f3e 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -64,13 +64,24 @@
*/
@Override
public boolean onShowTranslation(@NonNull View view) {
+ if (mIsShowingTranslation) {
+ if (DEBUG) {
+ Log.d(TAG, view + " is already showing translated text.");
+ }
+ return false;
+ }
ViewTranslationResponse response = view.getViewTranslationResponse();
if (response == null) {
Log.e(TAG, "onShowTranslation() shouldn't be called before "
+ "onViewTranslationResponse().");
return false;
}
- if (mTranslationTransformation == null) {
+ // It is possible user changes text and new translation response returns, system should
+ // update the translation response to keep the result up to date.
+ // Because TextView.setTransformationMethod() will skip the same TransformationMethod
+ // instance, we should create a new one to let new translation can work.
+ if (mTranslationTransformation == null
+ || !response.equals(mTranslationTransformation.getViewTranslationResponse())) {
TransformationMethod originalTranslationMethod =
((TextView) view).getTransformationMethod();
mTranslationTransformation = new TranslationTransformationMethod(response,
@@ -147,7 +158,7 @@
return true;
}
- boolean isShowingTranslation() {
+ public boolean isShowingTranslation() {
return mIsShowingTranslation;
}
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 8b8dba8..69bc1b5 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -88,4 +88,9 @@
* user has pressed back on the root activity of a task controlled by the task organizer.
*/
void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo);
+
+ /**
+ * Called when the IME has drawn on the organized task.
+ */
+ void onImeDrawnOnTask(int taskId);
}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index c7c91cd..d8723a8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -144,6 +144,10 @@
@BinderThread
public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {}
+ /** @hide */
+ @BinderThread
+ public void onImeDrawnOnTask(int taskId) {}
+
/**
* Creates a persistent root task in WM for a particular windowing-mode.
* @param displayId The display to create the root task on.
@@ -287,6 +291,11 @@
public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo info) {
mExecutor.execute(() -> TaskOrganizer.this.onBackPressedOnTaskRoot(info));
}
+
+ @Override
+ public void onImeDrawnOnTask(int taskId) {
+ mExecutor.execute(() -> TaskOrganizer.this.onImeDrawnOnTask(taskId));
+ }
};
private ITaskOrganizerController getController() {
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 901625b..6d0a6bd 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -26,7 +26,6 @@
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.os.IBinder;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -67,7 +66,7 @@
mType = type;
mOptions = options;
mWindowManager = createWindowContextWindowManager(this);
- IBinder token = getWindowContextToken();
+ WindowTokenClient token = (WindowTokenClient) getWindowContextToken();
mController = new WindowContextController(token);
Reference.reachabilityFence(this);
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index d84f571..505b450 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -46,7 +47,7 @@
@VisibleForTesting
public boolean mAttachedToDisplayArea;
@NonNull
- private final IBinder mToken;
+ private final WindowTokenClient mToken;
/**
* Window Context Controller constructor
@@ -54,14 +55,13 @@
* @param token The token used to attach to a window manager node. It is usually from
* {@link Context#getWindowContextToken()}.
*/
- public WindowContextController(@NonNull IBinder token) {
- mToken = token;
- mWms = WindowManagerGlobal.getWindowManagerService();
+ public WindowContextController(@NonNull WindowTokenClient token) {
+ this(token, WindowManagerGlobal.getWindowManagerService());
}
/** Used for test only. DO NOT USE it in production code. */
@VisibleForTesting
- public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+ public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) {
mToken = token;
mWms = mockWms;
}
@@ -81,8 +81,14 @@
+ "a DisplayArea once.");
}
try {
- mAttachedToDisplayArea = mWms.attachWindowContextToDisplayArea(mToken, type, displayId,
- options);
+ final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type,
+ displayId, options);
+ if (configuration != null) {
+ mAttachedToDisplayArea = true;
+ // Send the DisplayArea's configuration to WindowContext directly instead of
+ // waiting for dispatching from WMS.
+ mToken.onConfigurationChanged(configuration, displayId);
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 6abf557..4dcd2e7 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -24,6 +24,8 @@
import android.os.Bundle;
import android.os.IBinder;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.ref.WeakReference;
/**
@@ -33,7 +35,7 @@
* {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
*
* @see WindowContext
- * @see android.view.IWindowManager#registerWindowContextListener(IBinder, int, int, Bundle)
+ * @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
*
* @hide
*/
@@ -50,8 +52,8 @@
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
* can only attach one {@link Context}.
* <p>This method must be called before invoking
- * {@link android.view.IWindowManager#registerWindowContextListener(IBinder, int, int,
- * Bundle, boolean)}.<p/>
+ * {@link android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int,
+ * Bundle)}.<p/>
*
* @param context context to be attached
* @throws IllegalStateException if attached context has already existed.
@@ -63,6 +65,13 @@
mContextRef = new WeakReference<>(context);
}
+ /**
+ * Called when {@link Configuration} updates from the server side receive.
+ *
+ * @param newConfig the updated {@link Configuration}
+ * @param newDisplayId the updated {@link android.view.Display} ID
+ */
+ @VisibleForTesting
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
final Context context = mContextRef.get();
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 84354d9..ec224e5 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -72,17 +72,19 @@
private Resources mSuspendingAppResources;
private SuspendDialogInfo mSuppliedDialogInfo;
private Bundle mOptions;
- private BroadcastReceiver mUnsuspendReceiver = new BroadcastReceiver() {
+ private BroadcastReceiver mSuspendModifiedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(intent.getAction())) {
- final String[] unsuspended = intent.getStringArrayExtra(
+ if (Intent.ACTION_PACKAGES_SUSPENSION_CHANGED.equals(intent.getAction())) {
+ // Suspension conditions were modified, dismiss any related visible dialogs.
+ final String[] modified = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_PACKAGE_LIST);
- if (ArrayUtils.contains(unsuspended, mSuspendedPackage)) {
+ if (ArrayUtils.contains(modified, mSuspendedPackage)) {
if (!isFinishing()) {
- Slog.w(TAG, "Package " + mSuspendedPackage
- + " got unsuspended while the dialog was visible. Finishing.");
+ Slog.w(TAG, "Package " + mSuspendedPackage + " has modified"
+ + " suspension conditions while dialog was visible. Finishing.");
SuspendedAppActivity.this.finish();
+ // TODO (b/198201994): reload the suspend dialog to show most relevant info
}
}
}
@@ -245,15 +247,16 @@
setupAlert();
- final IntentFilter unsuspendFilter = new IntentFilter(Intent.ACTION_PACKAGES_UNSUSPENDED);
- registerReceiverAsUser(mUnsuspendReceiver, UserHandle.of(mUserId), unsuspendFilter, null,
- null);
+ final IntentFilter suspendModifiedFilter =
+ new IntentFilter(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED);
+ registerReceiverAsUser(mSuspendModifiedReceiver, UserHandle.of(mUserId),
+ suspendModifiedFilter, null, null);
}
@Override
protected void onDestroy() {
super.onDestroy();
- unregisterReceiver(mUnsuspendReceiver);
+ unregisterReceiver(mSuspendModifiedReceiver);
}
private void requestDismissKeyguardIfNeeded(CharSequence dismissMessage) {
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index ce75f45..068b882 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -165,7 +165,7 @@
// Now fetch app icon and raster with no badging even in work profile
Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info)
- .getIconBitmap(android.os.Process.myUserHandle());
+ .getIconBitmap(mContext.getUser());
// Raster target drawable with appIcon as a badge
SimpleIconFactory sif = SimpleIconFactory.obtain(mContext);
diff --git a/core/java/com/android/internal/infra/OWNERS b/core/java/com/android/internal/infra/OWNERS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/java/com/android/internal/infra/OWNERS
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 8e7fae7..d12c870 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -50,6 +50,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
/**
* A class that allows the app to get the frame metrics from HardwareRendererObserver.
@@ -103,6 +104,7 @@
private boolean mCancelled = false;
private FrameTrackerListener mListener;
private boolean mTracingStarted = false;
+ private Runnable mWaitForFinishTimedOut;
private static class JankInfo {
long frameVsyncId;
@@ -263,10 +265,16 @@
if (mListener != null) {
mListener.onCujEvents(mSession, ACTION_SESSION_END);
}
+ // We don't remove observer here,
+ // will remove it when all the frame metrics in this duration are called back.
+ // See onFrameMetricsAvailable for the logic of removing the observer.
+ // Waiting at most 10 seconds for all callbacks to finish.
+ mWaitForFinishTimedOut = () -> {
+ Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
+ finish(mJankInfos.size() - 1);
+ };
+ mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
}
- // We don't remove observer here,
- // will remove it when all the frame metrics in this duration are called back.
- // See onFrameMetricsAvailable for the logic of removing the observer.
}
/**
@@ -396,7 +404,8 @@
}
private void finish(int indexOnOrAfterEnd) {
-
+ mHandler.removeCallbacks(mWaitForFinishTimedOut);
+ mWaitForFinishTimedOut = null;
mMetricsFinalized = true;
// The tracing has been ended, remove the observer, see if need to trigger perfetto.
@@ -481,7 +490,7 @@
}
}
if (DEBUG) {
- Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName()
+ Log.i(TAG, "finish: CUJ=" + mSession.getName()
+ " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
+ " totalFrames=" + totalFramesCount
+ " missedAppFrames=" + missedAppFramesCount
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index aabcd7f..610cd73 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -103,7 +103,7 @@
private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName();
private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
- private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L);
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L);
private static final String SETTINGS_ENABLED_KEY = "enabled";
private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
diff --git a/core/java/com/android/internal/net/NetworkUtilsInternal.java b/core/java/com/android/internal/net/NetworkUtilsInternal.java
index 052959a..1b6cb29 100644
--- a/core/java/com/android/internal/net/NetworkUtilsInternal.java
+++ b/core/java/com/android/internal/net/NetworkUtilsInternal.java
@@ -74,35 +74,4 @@
return true;
}
-
- /**
- * Safely multiple a value by a rational.
- * <p>
- * Internally it uses integer-based math whenever possible, but switches
- * over to double-based math if values would overflow.
- * @hide
- */
- public static long multiplySafeByRational(long value, long num, long den) {
- if (den == 0) {
- throw new ArithmeticException("Invalid Denominator");
- }
- long x = value;
- long y = num;
-
- // Logic shamelessly borrowed from Math.multiplyExact()
- long r = x * y;
- long ax = Math.abs(x);
- long ay = Math.abs(y);
- if (((ax | ay) >>> 31 != 0)) {
- // Some bits greater than 2^31 that might cause overflow
- // Check the result using the divide operator
- // and check for the special case of Long.MIN_VALUE * -1
- if (((y != 0) && (r / y != x))
- || (x == Long.MIN_VALUE && y == -1)) {
- // Use double math to avoid overflowing
- return (long) (((double) num / den) * value);
- }
- }
- return r / den;
- }
}
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index 5f84b5a..d8dc143 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -143,17 +143,24 @@
public boolean areAuthParamsInline = false; // 23
public final boolean isRestrictedToTestNetworks; // 24
+ public final boolean excludeLocalRoutes; // 25
+
// Helper fields.
@UnsupportedAppUsage
public transient boolean saveLogin = false;
public VpnProfile(String key) {
- this(key, false);
+ this(key, false, false);
}
public VpnProfile(String key, boolean isRestrictedToTestNetworks) {
+ this(key, isRestrictedToTestNetworks, false);
+ }
+
+ public VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes) {
this.key = key;
this.isRestrictedToTestNetworks = isRestrictedToTestNetworks;
+ this.excludeLocalRoutes = excludeLocalRoutes;
}
@UnsupportedAppUsage
@@ -183,6 +190,7 @@
maxMtu = in.readInt();
areAuthParamsInline = in.readBoolean();
isRestrictedToTestNetworks = in.readBoolean();
+ excludeLocalRoutes = in.readBoolean();
}
/**
@@ -230,6 +238,7 @@
out.writeInt(maxMtu);
out.writeBoolean(areAuthParamsInline);
out.writeBoolean(isRestrictedToTestNetworks);
+ out.writeBoolean(excludeLocalRoutes);
}
/**
@@ -249,8 +258,9 @@
// 14-19: Standard profile, with option for serverCert, proxy
// 24: Standard profile with serverCert, proxy and platform-VPN parameters
// 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks
+ // 26: Standard profile with platform-VPN parameters and excludeLocalRoutes
if ((values.length < 14 || values.length > 19)
- && values.length != 24 && values.length != 25) {
+ && values.length != 24 && values.length != 25 && values.length != 26) {
return null;
}
@@ -261,7 +271,15 @@
isRestrictedToTestNetworks = false;
}
- VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks);
+ final boolean excludeLocalRoutes;
+ if (values.length >= 26) {
+ excludeLocalRoutes = Boolean.parseBoolean(values[25]);
+ } else {
+ excludeLocalRoutes = false;
+ }
+
+ VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks,
+ excludeLocalRoutes);
profile.name = values[0];
profile.type = Integer.parseInt(values[1]);
if (profile.type < 0 || profile.type > TYPE_MAX) {
@@ -371,6 +389,8 @@
builder.append(VALUE_DELIMITER).append(areAuthParamsInline);
builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks);
+ builder.append(VALUE_DELIMITER).append(excludeLocalRoutes);
+
return builder.toString().getBytes(StandardCharsets.UTF_8);
}
@@ -451,7 +471,7 @@
key, type, server, username, password, dnsServers, searchDomains, routes, mppe,
l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert,
proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline,
- isRestrictedToTestNetworks);
+ isRestrictedToTestNetworks, excludeLocalRoutes);
}
/** Checks VPN profiles for interior equality. */
@@ -484,7 +504,8 @@
&& isMetered == other.isMetered
&& maxMtu == other.maxMtu
&& areAuthParamsInline == other.areAuthParamsInline
- && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks;
+ && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks
+ && excludeLocalRoutes == other.excludeLocalRoutes;
}
@NonNull
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 630851d..0f1c6f3 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -707,6 +707,10 @@
* Mapping isolated uids to the actual owning app uid.
*/
final SparseIntArray mIsolatedUids = new SparseIntArray();
+ /**
+ * Internal reference count of isolated uids.
+ */
+ final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
/**
* The statistics we have collected organized by uids.
@@ -3897,6 +3901,7 @@
public void addIsolatedUidLocked(int isolatedUid, int appUid,
long elapsedRealtimeMs, long uptimeMs) {
mIsolatedUids.put(isolatedUid, appUid);
+ mIsolatedUidRefCounts.put(isolatedUid, 1);
final Uid u = getUidStatsLocked(appUid, elapsedRealtimeMs, uptimeMs);
u.addIsolatedUid(isolatedUid);
}
@@ -3915,19 +3920,51 @@
}
/**
- * This should only be called after the cpu times have been read.
+ * Isolated uid should only be removed after all wakelocks associated with the uid are stopped
+ * and the cpu time-in-state has been read one last time for the uid.
+ *
* @see #scheduleRemoveIsolatedUidLocked(int, int)
+ *
+ * @return true if the isolated uid is actually removed.
*/
@GuardedBy("this")
- public void removeIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, long uptimeMs) {
+ public boolean maybeRemoveIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs,
+ long uptimeMs) {
+ final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1;
+ if (refCount > 0) {
+ // Isolated uid is still being tracked
+ mIsolatedUidRefCounts.put(isolatedUid, refCount);
+ return false;
+ }
+
final int idx = mIsolatedUids.indexOfKey(isolatedUid);
if (idx >= 0) {
final int ownerUid = mIsolatedUids.valueAt(idx);
final Uid u = getUidStatsLocked(ownerUid, elapsedRealtimeMs, uptimeMs);
u.removeIsolatedUid(isolatedUid);
mIsolatedUids.removeAt(idx);
+ mIsolatedUidRefCounts.delete(isolatedUid);
+ } else {
+ Slog.w(TAG, "Attempted to remove untracked isolated uid (" + isolatedUid + ")");
}
mPendingRemovedUids.add(new UidToRemove(isolatedUid, elapsedRealtimeMs));
+
+ return true;
+ }
+
+ /**
+ * Increment the ref count for an isolated uid.
+ * call #maybeRemoveIsolatedUidLocked to decrement.
+ */
+ public void incrementIsolatedUidRefCount(int uid) {
+ final int refCount = mIsolatedUidRefCounts.get(uid, 0);
+ if (refCount <= 0) {
+ // Uid is not mapped or referenced
+ Slog.w(TAG,
+ "Attempted to increment ref counted of untracked isolated uid (" + uid + ")");
+ return;
+ }
+ mIsolatedUidRefCounts.put(uid, refCount + 1);
}
public int mapUid(int uid) {
@@ -4287,7 +4324,7 @@
public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
int type, boolean unimportantForLogging, long elapsedRealtimeMs, long uptimeMs) {
- uid = mapUid(uid);
+ final int mappedUid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
// Only care about partial wake locks, since full wake locks
// will be canceled when the user puts the screen to sleep.
@@ -4297,9 +4334,9 @@
}
if (mRecordAllHistory) {
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
- uid, 0)) {
+ mappedUid, 0)) {
addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
- HistoryItem.EVENT_WAKE_LOCK_START, historyName, uid);
+ HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
@@ -4308,7 +4345,7 @@
+ Integer.toHexString(mHistoryCur.states));
mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
- mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+ mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = mappedUid;
mWakeLockImportant = !unimportantForLogging;
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
} else if (!mWakeLockImportant && !unimportantForLogging
@@ -4318,14 +4355,19 @@
mHistoryLastWritten.wakelockTag = null;
mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
- mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+ mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = mappedUid;
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
mWakeLockImportant = true;
}
mWakeLockNesting++;
}
- if (uid >= 0) {
+ if (mappedUid >= 0) {
+ if (mappedUid != uid) {
+ // Prevent the isolated uid mapping from being removed while the wakelock is
+ // being held.
+ incrementIsolatedUidRefCount(uid);
+ }
if (mOnBatteryScreenOffTimeBase.isRunning()) {
// We only update the cpu time when a wake lock is acquired if the screen is off.
// If the screen is on, we don't distribute the power amongst partial wakelocks.
@@ -4335,7 +4377,7 @@
requestWakelockCpuUpdate();
}
- getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+ getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
if (wc != null) {
@@ -4343,8 +4385,8 @@
wc.getTags(), getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
} else {
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, uid,
- null, getPowerManagerWakeLockLevel(type), name,
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+ mappedUid, null, getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
}
}
@@ -4358,7 +4400,7 @@
public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
int type, long elapsedRealtimeMs, long uptimeMs) {
- uid = mapUid(uid);
+ final int mappedUid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
mWakeLockNesting--;
if (mRecordAllHistory) {
@@ -4366,9 +4408,9 @@
historyName = name;
}
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
- uid, 0)) {
+ mappedUid, 0)) {
addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
- HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, uid);
+ HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
@@ -4380,7 +4422,7 @@
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
}
- if (uid >= 0) {
+ if (mappedUid >= 0) {
if (mOnBatteryScreenOffTimeBase.isRunning()) {
if (DEBUG_ENERGY_CPU) {
Slog.d(TAG, "Updating cpu time because of -wake_lock");
@@ -4388,17 +4430,22 @@
requestWakelockCpuUpdate();
}
- getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+ getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
if (wc != null) {
FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
wc.getTags(), getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
} else {
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, uid,
- null, getPowerManagerWakeLockLevel(type), name,
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+ mappedUid, null, getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
}
+
+ if (mappedUid != uid) {
+ // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
+ maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
+ }
}
}
@@ -8571,7 +8618,7 @@
* inactive so can be dropped.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public boolean reset(long uptimeUs, long realtimeUs) {
+ public boolean reset(long uptimeUs, long realtimeUs, int resetReason) {
boolean active = false;
mOnBatteryBackgroundTimeBase.init(uptimeUs, realtimeUs);
@@ -8641,7 +8688,11 @@
resetIfNotNull(mBluetoothControllerActivity, false, realtimeUs);
resetIfNotNull(mModemControllerActivity, false, realtimeUs);
- MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+ if (resetReason == RESET_REASON_MEASURED_ENERGY_BUCKETS_CHANGE) {
+ mUidMeasuredEnergyStats = null;
+ } else {
+ MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+ }
resetIfNotNull(mUserCpuTime, false, realtimeUs);
resetIfNotNull(mSystemCpuTime, false, realtimeUs);
@@ -11324,7 +11375,7 @@
mNumConnectivityChange = 0;
for (int i=0; i<mUidStats.size(); i++) {
- if (mUidStats.valueAt(i).reset(uptimeUs, elapsedRealtimeUs)) {
+ if (mUidStats.valueAt(i).reset(uptimeUs, elapsedRealtimeUs, resetReason)) {
mUidStats.valueAt(i).detachFromTimeBase();
mUidStats.remove(mUidStats.keyAt(i));
i--;
@@ -16761,6 +16812,15 @@
pw.print("UIDs removed since the later of device start or stats reset: ");
pw.println(mNumUidsRemoved);
+ pw.println("Currently mapped isolated uids:");
+ final int numIsolatedUids = mIsolatedUids.size();
+ for (int i = 0; i < numIsolatedUids; i++) {
+ final int isolatedUid = mIsolatedUids.keyAt(i);
+ final int ownerUid = mIsolatedUids.valueAt(i);
+ final int refCount = mIsolatedUidRefCounts.get(isolatedUid);
+ pw.println(" " + isolatedUid + "->" + ownerUid + " (ref count = " + refCount + ")");
+ }
+
pw.println();
dumpConstantsLocked(pw);
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 0038579..980aec1 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -29,6 +29,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -234,8 +235,9 @@
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
+ final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames();
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- mStats.getCustomEnergyConsumerNames(), includePowerModels);
+ customEnergyConsumerNames, includePowerModels);
if (mBatteryUsageStatsStore == null) {
Log.e(TAG, "BatteryUsageStatsStore is unavailable");
return builder.build();
@@ -247,7 +249,14 @@
final BatteryUsageStats snapshot =
mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
if (snapshot != null) {
- builder.add(snapshot);
+ if (Arrays.equals(snapshot.getCustomPowerComponentNames(),
+ customEnergyConsumerNames)) {
+ builder.add(snapshot);
+ } else {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
+ + "custom power components: "
+ + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+ }
}
}
}
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index 9b51a8e..bb307a0 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -21,13 +21,18 @@
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
+import android.util.Slog;
import android.util.SparseArray;
+import java.util.Arrays;
+
/**
* Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
* {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
*/
public class CustomMeasuredPowerCalculator extends PowerCalculator {
+ private static final String TAG = "CustomMeasuredPowerCalc";
+
public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
}
@@ -76,9 +81,9 @@
if (totalPowerMah == null) {
newTotalPowerMah = new double[customMeasuredPowerMah.length];
} else if (totalPowerMah.length != customMeasuredPowerMah.length) {
- newTotalPowerMah = new double[customMeasuredPowerMah.length];
- System.arraycopy(totalPowerMah, 0, newTotalPowerMah, 0,
- customMeasuredPowerMah.length);
+ Slog.wtf(TAG, "Number of custom energy components is not the same for all apps: "
+ + totalPowerMah.length + ", " + customMeasuredPowerMah.length);
+ newTotalPowerMah = Arrays.copyOf(totalPowerMah, customMeasuredPowerMah.length);
} else {
newTotalPowerMah = totalPowerMah;
}
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 776a705..3915b0e 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -215,6 +215,9 @@
+ "ms tx=" + txTime + "ms power=" + formatCharge(
powerDurationAndTraffic.powerMah));
}
+ } else {
+ powerDurationAndTraffic.durationMs = 0;
+ powerDurationAndTraffic.powerMah = 0;
}
} else {
final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 6b9d95f..611f644 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -713,7 +713,7 @@
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
- + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011",
+ + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011,3012",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index b321ac0..a09c823 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -124,7 +124,6 @@
Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
- sendCloseSystemWindows();
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
startCallActivity();
@@ -147,7 +146,6 @@
dispatcher.performedLongPress(event);
if (isUserSetupComplete()) {
mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- sendCloseSystemWindows();
// Broadcast an intent that the Camera button was longpressed
Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -178,7 +176,6 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- sendCloseSystemWindows();
getSearchManager().stopSearch();
mContext.startActivity(intent);
// Only clear this if we successfully start the
@@ -272,7 +269,6 @@
@UnsupportedAppUsage
void startCallActivity() {
- sendCloseSystemWindows();
Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
@@ -319,10 +315,6 @@
return mMediaSessionManager;
}
- void sendCloseSystemWindows() {
- PhoneWindow.sendCloseSystemWindows(mContext, null);
- }
-
private void handleVolumeKeyEvent(KeyEvent keyEvent) {
getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent,
AudioManager.USE_DEFAULT_STREAM_TYPE);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 10f14b4..ed6415d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -148,7 +148,7 @@
*/
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId,
- String opPackageName, long operationId, int multiSensorConfig);
+ long operationId, String opPackageName, long requestId, int multiSensorConfig);
/**
* Used to notify the authentication dialog that a biometric has been authenticated.
*/
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index e7d6d6c..b3499db 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -110,7 +110,8 @@
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId, int multiSensorConfig);
+ int userId, long operationId, String opPackageName, long requestId,
+ int multiSensorConfig);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();
diff --git a/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl b/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
new file mode 100644
index 0000000..6ca8cec
--- /dev/null
+++ b/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.internal.telephony;
+
+oneway interface ICarrierPrivilegesListener {
+ void onCarrierPrivilegesChanged(
+ in List<String> privilegedPackageNames, in int[] privilegedUids);
+}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index a0a0f32..9712d7e 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,7 @@
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
@@ -43,7 +44,8 @@
void removeOnSubscriptionsChangedListener(String pkg,
IOnSubscriptionsChangedListener callback);
- void listenWithEventList(in int subId, String pkg, String featureId,
+ void listenWithEventList(in boolean renounceFineLocationAccess,
+ in boolean renounceCoarseLocationAccess, in int subId, String pkg, String featureId,
IPhoneStateListener callback, in int[] events, boolean notifyNow);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void notifyCallStateForAllSubs(int state, String incomingNumber);
@@ -76,6 +78,7 @@
void notifySubscriptionInfoChanged();
void notifyOpportunisticSubscriptionInfoChanged();
void notifyCarrierNetworkChange(in boolean active);
+ void notifyCarrierNetworkChangeWithSubId(in int subId, in boolean active);
void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
void notifyDisplayInfoChanged(int slotIndex, int subId, in TelephonyDisplayInfo telephonyDisplayInfo);
void notifyPhoneCapabilityChanged(in PhoneCapability capability);
@@ -98,4 +101,10 @@
void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
in List<LinkCapacityEstimate> linkCapacityEstimateList);
+
+ void addCarrierPrivilegesListener(
+ int phoneId, ICarrierPrivilegesListener callback, String pkg, String featureId);
+ void removeCarrierPrivilegesListener(ICarrierPrivilegesListener callback, String pkg);
+ void notifyCarrierPrivilegesChanged(
+ int phoneId, in List<String> privilegedPackageNames, in int[] privilegedUids);
}
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 8b3c133..7a712e5 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -291,10 +291,10 @@
* Finds a suitable color such that there's enough contrast.
*
* @param color the color to start searching from.
- * @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
- * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+ * @param other the color to ensure contrast against. Assumed to be lighter than {@code color}
+ * @param findFg if true, we assume {@code color} is a foreground, otherwise a background.
* @param minRatio the minimum contrast ratio required.
- * @return a color with the same hue as {@param color}, potentially darkened to meet the
+ * @return a color with the same hue as {@code color}, potentially darkened to meet the
* contrast ratio.
*/
public static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
@@ -331,7 +331,7 @@
* @param color the color to start searching from.
* @param backgroundColor the color to ensure contrast against.
* @param minRatio the minimum contrast ratio required.
- * @return the same color as {@param color} with potentially modified alpha to meet contrast
+ * @return the same color as {@code color} with potentially modified alpha to meet contrast
*/
public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) {
int fg = color;
@@ -361,10 +361,10 @@
* Finds a suitable color such that there's enough contrast.
*
* @param color the color to start searching from.
- * @param other the color to ensure contrast against. Assumed to be darker than {@param color}
- * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+ * @param other the color to ensure contrast against. Assumed to be darker than {@code color}
+ * @param findFg if true, we assume {@code color} is a foreground, otherwise a background.
* @param minRatio the minimum contrast ratio required.
- * @return a color with the same hue as {@param color}, potentially darkened to meet the
+ * @return a color with the same hue as {@code color}, potentially lightened to meet the
* contrast ratio.
*/
public static int findContrastColorAgainstDark(int color, int other, boolean findFg,
@@ -393,7 +393,8 @@
low = l;
}
}
- return findFg ? fg : bg;
+ hsl[2] = high;
+ return ColorUtilsFromCompat.HSLToColor(hsl);
}
public static int ensureTextContrastOnBlack(int color) {
@@ -452,7 +453,7 @@
}
/**
- * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
+ * Resolves {@code color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
*/
public static int resolveColor(Context context, int color, boolean defaultBackgroundIsDark) {
if (color == Notification.COLOR_DEFAULT) {
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 100a605d..2b7f8b2 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -4,4 +4,3 @@
per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
per-file Protocol* = etancohen@google.com, lorenzo@google.com
per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
-per-file DataClass* = eugenesusla@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/function/pooled/OWNERS b/core/java/com/android/internal/util/function/pooled/OWNERS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/java/com/android/internal/util/function/pooled/OWNERS
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 02ffe8c..040d78b 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -92,7 +92,7 @@
// contribution to mTextView's nested batch edit count is zero.
mTextView.endBatchEdit();
mBatchEditNesting--;
- return true;
+ return mBatchEditNesting > 0;
}
}
return false;
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 498505c..cd1bbb6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,6 +17,7 @@
package com.android.internal.widget;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -1272,6 +1273,14 @@
}
/**
+ * Whether the user is not allowed to set any credentials via PASSWORD_QUALITY_MANAGED.
+ */
+ public boolean isCredentialsDisabledForUser(int userId) {
+ return getDevicePolicyManager().getPasswordQuality(/* admin= */ null, userId)
+ == PASSWORD_QUALITY_MANAGED;
+ }
+
+ /**
* @see StrongAuthTracker#isTrustAllowedForUser
*/
public boolean isTrustAllowedForUser(int userId) {
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 0c2d2a9..3191e23 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -53,6 +53,8 @@
private int mEmphasizedHeight;
private int mRegularHeight;
@DimenRes private int mCollapsibleIndentDimen = R.dimen.notification_actions_padding_start;
+ int mNumNotGoneChildren;
+ int mNumPriorityChildren;
public NotificationActionListLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -76,15 +78,14 @@
&& ((EmphasizedNotificationButton) actionView).isPriority();
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int N = getChildCount();
+ private void countAndRebuildMeasureOrder() {
+ final int numChildren = getChildCount();
int textViews = 0;
int otherViews = 0;
- int notGoneChildren = 0;
- int priorityChildren = 0;
+ mNumNotGoneChildren = 0;
+ mNumPriorityChildren = 0;
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numChildren; i++) {
View c = getChildAt(i);
if (c instanceof TextView) {
textViews++;
@@ -92,9 +93,9 @@
otherViews++;
}
if (c.getVisibility() != GONE) {
- notGoneChildren++;
+ mNumNotGoneChildren++;
if (isPriority(c)) {
- priorityChildren++;
+ mNumPriorityChildren++;
}
}
}
@@ -119,17 +120,20 @@
if (needRebuild) {
rebuildMeasureOrder(textViews, otherViews);
}
+ }
+ private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
+ boolean collapsePriorityActions) {
+ final int numChildren = getChildCount();
final boolean constrained =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED;
-
- final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
final int otherSize = mMeasureOrderOther.size();
int usedWidth = 0;
+ int maxPriorityWidth = 0;
int measuredChildren = 0;
int measuredPriorityChildren = 0;
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numChildren; i++) {
// Measure shortest children first. To avoid measuring twice, we approximate by looking
// at the text length.
final boolean isPriority;
@@ -154,12 +158,20 @@
// measure in the order of (approx.) size, a large view can still take more than its
// share if the others are small.
int availableWidth = innerWidth - usedWidth;
- int unmeasuredChildren = notGoneChildren - measuredChildren;
+ int unmeasuredChildren = mNumNotGoneChildren - measuredChildren;
int maxWidthForChild = availableWidth / unmeasuredChildren;
- if (isPriority) {
+ if (isPriority && collapsePriorityActions) {
+ // Collapsing the actions to just the width required to show the icon.
+ if (maxPriorityWidth == 0) {
+ maxPriorityWidth = getResources().getDimensionPixelSize(
+ R.dimen.notification_actions_collapsed_priority_width);
+ }
+ maxWidthForChild = maxPriorityWidth + lp.leftMargin + lp.rightMargin;
+ } else if (isPriority) {
// Priority children get a larger maximum share of the total space:
// maximum priority share = (nPriority + 1) / (MAX + 1)
- int unmeasuredPriorityChildren = priorityChildren - measuredPriorityChildren;
+ int unmeasuredPriorityChildren = mNumPriorityChildren
+ - measuredPriorityChildren;
int unmeasuredOtherChildren = unmeasuredChildren - unmeasuredPriorityChildren;
int widthReservedForOtherChildren = innerWidth * unmeasuredOtherChildren
/ (Notification.MAX_ACTION_BUTTONS + 1);
@@ -187,6 +199,19 @@
} else {
mExtraStartPadding = 0;
}
+ return usedWidth;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ countAndRebuildMeasureOrder();
+ final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
+ int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ false /* collapsePriorityButtons */);
+ if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ true /* collapsePriorityButtons */);
+ }
mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS
index 554e278..81bad6a 100644
--- a/core/java/com/android/server/OWNERS
+++ b/core/java/com/android/server/OWNERS
@@ -1 +1 @@
-per-file SystemConfig.java = toddke@google.com,patb@google.com
+per-file SystemConfig.java = patb@google.com
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index a131111..0f98b74 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -228,8 +228,7 @@
// TODO: Rename the server-level flag or remove.
static const char* ENABLE_JITZYGOTE_IMAGE = "enable_apex_image";
// Flag to pass to the runtime when using the JIT Zygote image.
-static const char* kJitZygoteImageOption =
- "-Ximage:boot.art:/nonx/boot-framework.art!/system/etc/boot-image.prof";
+static const char* kJitZygoteImageOption = "-Xforcejitzygote";
// Feature flag name for disabling lock profiling.
static const char* DISABLE_LOCK_PROFILING = "disable_lock_profiling";
@@ -983,9 +982,9 @@
"--instruction-set-features=", "-Xcompiler-option");
/*
- * When running with debug.generate-debug-info, add --generate-debug-info to
- * the compiler options so that both JITted code and the boot image extension,
- * if it is compiled on device, will include native debugging information.
+ * When running with debug.generate-debug-info, add --generate-debug-info to the compiler
+ * options so that both JITted code and the boot image, if it is compiled on device, will
+ * include native debugging information.
*/
property_get("debug.generate-debug-info", propBuf, "");
bool generate_debug_info = (strcmp(propBuf, "true") == 0);
@@ -1008,7 +1007,7 @@
property_get("dalvik.vm.extra-opts", extraOptsBuf, "");
parseExtraOpts(extraOptsBuf, NULL);
- // Extra options for boot image extension generation.
+ // Extra options for boot image generation.
if (skip_compilation) {
addOption("-Xnoimage-dex2oat");
} else {
@@ -1031,8 +1030,8 @@
parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=",
"-Ximage-compiler-option");
- // The runtime may compile a boot image extension, when necessary, not using installd.
- // Thus, we need to pass the instruction-set-features/variant as an image-compiler-option.
+ // The runtime may compile a boot image, when necessary, not using installd. Thus, we need
+ // to pass the instruction-set-features/variant as an image-compiler-option.
// Note: it is OK to reuse the buffer, as the values are exactly the same between
// * compiler-option, used for runtime compilation (DexClassLoader)
// * image-compiler-option, used for boot-image compilation on device
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 65d073f..832c498 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -3,7 +3,7 @@
per-file *Camera*,*camera* = shuzhenwang@google.com, zhijunhe@google.com
# Connectivity
-per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
+per-file android_net_* = jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
# CPU
per-file *Cpu* = file:/core/java/com/android/internal/os/CPU_OWNERS
@@ -55,13 +55,17 @@
per-file android_media_midi_* = file:/media/java/android/media/midi/OWNERS
per-file android_opengl_* = file:/opengl/java/android/opengl/OWNERS
per-file android_os_storage_* = file:/core/java/android/os/storage/OWNERS
-per-file android_se_* = file:/core/java/android/se/OWNERS
+per-file android_se_* = file:/omapi/java/android/se/OWNERS
per-file android_security_* = file:/core/java/android/security/OWNERS
per-file android_view_* = file:/core/java/android/view/OWNERS
per-file com_android_internal_net_* = file:/services/core/java/com/android/server/net/OWNERS
### Graphics ###
per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
+
+### Text ###
+per-file android_text_* = file:/core/java/android/text/OWNERS
+
# These are highly common-use files
per-file Android.bp = file:/graphics/java/android/graphics/OWNERS
per-file AndroidRuntime.cpp = file:/graphics/java/android/graphics/OWNERS
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index b40491a..f44e829 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,8 +50,7 @@
}
void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
- jstring devOptIn, jobjectArray featuresObj, jobject rulesFd,
- jlong rulesOffset, jlong rulesLength) {
+ jstring devOptIn, jobjectArray featuresObj) {
ScopedUtfChars pathChars(env, path);
ScopedUtfChars appNameChars(env, appName);
ScopedUtfChars devOptInChars(env, devOptIn);
@@ -74,11 +73,8 @@
}
}
- int rulesFd_native = jniGetFDFromFileDescriptor(env, rulesFd);
-
android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
- devOptInChars.c_str(), features,
- rulesFd_native, rulesOffset, rulesLength);
+ devOptInChars.c_str(), features);
}
bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
@@ -124,8 +120,7 @@
{"setInjectLayersPrSetDumpable", "()Z",
reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
{"setAngleInfo",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/io/"
- "FileDescriptor;JJ)V",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
reinterpret_cast<void*>(setAngleInfo_native)},
{"getShouldUseAngle", "(Ljava/lang/String;)Z",
reinterpret_cast<void*>(shouldUseAngle_native)},
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index f67007c..85fd5d9 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -90,6 +90,22 @@
atrace_set_tracing_enabled(enabled);
}
+static void android_os_Trace_nativeInstant(JNIEnv* env, jclass,
+ jlong tag, jstring nameStr) {
+ withString(env, nameStr, [tag](char* str) {
+ atrace_instant(tag, str);
+ });
+}
+
+static void android_os_Trace_nativeInstantForTrack(JNIEnv* env, jclass,
+ jlong tag, jstring trackStr, jstring nameStr) {
+ withString(env, trackStr, [env, tag, nameStr](char* track) {
+ withString(env, nameStr, [tag, track](char* name) {
+ atrace_instant_for_track(tag, track, name);
+ });
+ });
+}
+
static const JNINativeMethod gTraceMethods[] = {
/* name, signature, funcPtr */
{ "nativeSetAppTracingAllowed",
@@ -116,6 +132,12 @@
{ "nativeAsyncTraceEnd",
"(JLjava/lang/String;I)V",
(void*)android_os_Trace_nativeAsyncTraceEnd },
+ { "nativeInstant",
+ "(JLjava/lang/String;)V",
+ (void*)android_os_Trace_nativeInstant },
+ { "nativeInstantForTrack",
+ "(JLjava/lang/String;Ljava/lang/String;)V",
+ (void*)android_os_Trace_nativeInstantForTrack },
// ----------- @CriticalNative ----------------
{ "nativeGetEnabledTags",
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index a699f91..7d0f60a 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -447,10 +447,6 @@
skipCallbacks = true;
}
}
-
- if (skipCallbacks) {
- mInputConsumer.sendFinishedSignal(seq, false);
- }
}
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 0957067..869b53d 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -449,6 +449,11 @@
int(changeFrameRateStrategy));
}
+static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) {
+ sp<Surface> surface(reinterpret_cast<Surface*>(nativeObject));
+ surface->destroy();
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gSurfaceMethods[] = {
@@ -477,6 +482,7 @@
{"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
{"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
{"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+ {"nativeDestroy", "(J)V", (void*)nativeDestroy},
};
int register_android_view_Surface(JNIEnv* env)
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 78650ed..931ef44 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -5,7 +5,6 @@
singhtejinder@google.com
yanmin@google.com
yaochen@google.com
-yro@google.com
zhouwenjie@google.com
# Frameworks
@@ -13,7 +12,7 @@
jjaggi@google.com
kwekua@google.com
roosa@google.com
-per-file package_item_info.proto = toddke@google.com,patb@google.com
+per-file package_item_info.proto = patb@google.com
per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index ed3968a..e035ab1 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -716,8 +716,6 @@
optional SettingProto nr_nsa_tracking_screen_off_mode = 153 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto nsd_on = 83 [ (android.privacy).dest = DEST_AUTOMATIC ];
-
message Ntp {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -784,6 +782,8 @@
optional SettingProto power_manager_constants = 93;
reserved 94; // Used to be priv_app_oob_enabled
+ optional SettingProto power_button_long_press_duration_ms = 154 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
message PrepaidSetup {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -983,6 +983,7 @@
optional SettingProto usb_mass_storage_enabled = 127 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto use_google_mail = 128 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto use_open_wifi_package = 129 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto uwb_enabled = 155 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto vt_ims_enabled = 130 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto wait_for_debugger = 131 [ (android.privacy).dest = DEST_AUTOMATIC ];
@@ -1076,5 +1077,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 154;
+ // Next tag = 156;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1a38ea1..6ecfcec 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -55,6 +55,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENSION_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
<protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" />
<protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
@@ -2018,7 +2019,7 @@
<permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
android:protectionLevel="signature|privileged" />
- <!-- Control access to email providers exclusively for Bluetooth
+ <!-- @SystemApi Control access to email providers exclusively for Bluetooth
@hide
-->
<permission android:name="android.permission.BLUETOOTH_MAP"
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 165dcad..e241dd6 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -22,7 +22,6 @@
shanh@google.com
svetoslavganov@android.com
svetoslavganov@google.com
-toddke@google.com
tsuji@google.com
yamasani@google.com
diff --git a/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
index 5add19b..aa38000 100644
--- a/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
@@ -18,19 +18,9 @@
-->
<!-- This should be kept in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:hasRoundedCorners="true"
android:shareInterpolator="false"
- android:zAdjustment="top">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -38,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the thumbnail around longer and fade out the thumbnail -->
<alpha android:fromAlpha="1.0" android:toAlpha="0"
diff --git a/core/res/res/anim-ldrtl/task_close_enter.xml b/core/res/res/anim-ldrtl/task_close_enter.xml
index e00141a..5ace46d 100644
--- a/core/res/res/anim-ldrtl/task_close_enter.xml
+++ b/core/res/res/anim-ldrtl/task_close_enter.xml
@@ -14,20 +14,9 @@
~ limitations under the License
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -35,34 +24,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim-ldrtl/task_close_exit.xml b/core/res/res/anim-ldrtl/task_close_exit.xml
index 71a44ae..0887019 100644
--- a/core/res/res/anim-ldrtl/task_close_exit.xml
+++ b/core/res/res/anim-ldrtl/task_close_exit.xml
@@ -15,19 +15,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -35,26 +24,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_enter.xml b/core/res/res/anim-ldrtl/task_open_enter.xml
index 7815f7d..52c74a6 100644
--- a/core/res/res/anim-ldrtl/task_open_enter.xml
+++ b/core/res/res/anim-ldrtl/task_open_enter.xml
@@ -16,20 +16,9 @@
<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
<!-- This should in sync with cross_profile_apps_thumbnail_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,34 +26,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
-</set>
\ No newline at end of file
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml b/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
index 5fccd6df..90ec071 100644
--- a/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
@@ -16,20 +16,9 @@
-->
<!-- This should in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,36 +26,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the transition around longer for the thumbnail, should be kept in sync with
cross_profile_apps_thumbmail.xml -->
@@ -75,4 +37,4 @@
android:toAlpha="1.0"
android:startOffset="717"
android:duration="200"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_exit.xml b/core/res/res/anim-ldrtl/task_open_exit.xml
index 025e1bd..88cdcce 100644
--- a/core/res/res/anim-ldrtl/task_open_exit.xml
+++ b/core/res/res/anim-ldrtl/task_open_exit.xml
@@ -15,19 +15,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -35,26 +24,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
index 2cfeecf..f6d7b72 100644
--- a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
@@ -19,18 +19,8 @@
<!-- This should be kept in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:zAdjustment="top">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -38,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the thumbnail around longer and fade out the thumbnail -->
<alpha android:fromAlpha="1.0" android:toAlpha="0"
diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml
index 487ff5c..52017b1 100644
--- a/core/res/res/anim/task_close_enter.xml
+++ b/core/res/res/anim/task_close_enter.xml
@@ -16,20 +16,9 @@
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,34 +26,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml
index afc3256c..3a8dd93 100644
--- a/core/res/res/anim/task_close_exit.xml
+++ b/core/res/res/anim/task_close_exit.xml
@@ -17,19 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -37,26 +26,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
index 0aafc1c..3c93438 100644
--- a/core/res/res/anim/task_open_enter.xml
+++ b/core/res/res/anim/task_open_enter.xml
@@ -15,23 +15,12 @@
** limitations under the License.
*/
-->
-<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
-<!-- This should in sync with cross_profile_apps_thumbnail_enter.xml -->
+<!-- This should be kept in sync with task_open_enter_cross_profile_apps.xml -->
+<!-- This should be kept in sync with cross_profile_apps_thumbnail_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -39,34 +28,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/task_open_enter_cross_profile_apps.xml b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
index 702f7ba..16249d1 100644
--- a/core/res/res/anim/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
@@ -18,20 +18,9 @@
-->
<!-- This should in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -39,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the transition around longer for the thumbnail, should be kept in sync with
cross_profile_apps_thumbmail.xml -->
diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml
index 691317d..21fec7f 100644
--- a/core/res/res/anim/task_open_exit.xml
+++ b/core/res/res/anim/task_open_exit.xml
@@ -17,19 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -37,26 +26,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set>
\ No newline at end of file
+</set>
diff --git a/core/res/res/color/overview_background.xml b/core/res/res/color/overview_background.xml
new file mode 100644
index 0000000..45c6c25
--- /dev/null
+++ b/core/res/res/color/overview_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral2_500" android:lStar="87" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/overview_background_dark.xml b/core/res/res/color/overview_background_dark.xml
new file mode 100644
index 0000000..84f4fdf
--- /dev/null
+++ b/core/res/res/color/overview_background_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral2_500" android:lStar="35" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml
index 29c51f2a..7c09fb8 100644
--- a/core/res/res/drawable/btn_notification_emphasized.xml
+++ b/core/res/res/drawable/btn_notification_emphasized.xml
@@ -24,9 +24,9 @@
android:insetBottom="@dimen/button_inset_vertical_material">
<shape android:shape="rectangle">
<corners android:radius="@dimen/notification_action_button_radius" />
- <padding android:left="12dp"
+ <padding android:left="16dp"
android:top="@dimen/button_padding_vertical_material"
- android:right="12dp"
+ android:right="16dp"
android:bottom="@dimen/button_padding_vertical_material" />
<solid android:color="@color/white" />
</shape>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 2e4578c..783fabe 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -33,4 +33,6 @@
<color name="call_notification_answer_color">#5DBA80</color>
<color name="personal_apps_suspension_notification_color">#8AB4F8</color>
+
+ <color name="overview_background">@color/overview_background_dark</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8496a47..fb7cdda 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.
-->
@@ -4497,6 +4511,13 @@
If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
<integer name="config_fixedRefreshRateInHighZone">0</integer>
+ <!-- Default refresh rate while the device has high brightness mode enabled for Sunlight.
+ This value overrides values from DisplayDeviceConfig -->
+ <integer name="config_defaultRefreshRateInHbmSunlight">0</integer>
+
+ <!-- Default refresh rate while the device has high brightness mode enabled for HDR. -->
+ <integer name="config_defaultRefreshRateInHbmHdr">0</integer>
+
<!-- The type of the light sensor to be used by the display framework for things like
auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. -->
<string name="config_displayLightSensorType" translatable="false" />
@@ -4556,6 +4577,13 @@
<!-- Indicates whether device has a power button fingerprint sensor. -->
<bool name="config_is_powerbutton_fps" translatable="false" >false</bool>
+ <!-- When each intermediate UDFPS enroll stage ends, as a fraction of total progress. -->
+ <string-array name="config_udfps_enroll_stage_thresholds" translatable="false">
+ <item>0.25</item>
+ <item>0.5</item>
+ <item>0.75</item>
+ </string-array>
+
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index de7a117..a666a5b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -58,6 +58,9 @@
<!-- How much we expand the touchable region of the status bar below the notch to catch touches
that just start below the notch. -->
<dimen name="display_cutout_touchable_region_size">12dp</dimen>
+ <!-- The default margin used in immersive mode to capture the start of a swipe gesture from the
+ edge of the screen to show the system bars. -->
+ <dimen name="system_gestures_start_threshold">24dp</dimen>
<!-- Height of the bottom navigation bar frame; this is different than navigation_bar_height
where that is the height reported to all the other windows to resize themselves around the
@@ -237,6 +240,9 @@
value is calculated in ConversationLayout#updateActionListPadding() -->
<dimen name="notification_actions_padding_start">36dp</dimen>
+ <!-- The max width of a priority action button when it is collapsed to just the icon. -->
+ <dimen name="notification_actions_collapsed_priority_width">60dp</dimen>
+
<!-- The start padding to optionally use (e.g. if there's extra space) for CallStyle
notification actions.
this = conversation_content_start (80dp) - button inset (4dp) - action padding (12dp) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8eede56..96cd839 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1736,6 +1736,8 @@
<!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings -->
<string-array name="fingerprint_error_vendor">
</string-array>
+ <!-- Default error message to use when fingerprint_error_vendor does not contain a message. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_error_vendor_unknown">Something went wrong. Try again.</string>
<!-- Content description which should be used for the fingerprint icon. -->
<string name="fingerprint_icon_content_description">Fingerprint icon</string>
@@ -1846,6 +1848,8 @@
<!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings -->
<string-array name="face_error_vendor">
</string-array>
+ <!-- Default error message to use when face_error_vendor does not contain a message. [CHAR LIMIT=NONE] -->
+ <string name="face_error_vendor_unknown">Something went wrong. Try again.</string>
<!-- Content description which should be used for the face icon. [CHAR LIMIT=10] -->
<string name="face_icon_content_description">Face icon</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d06dafe..8ed7991 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -439,6 +439,8 @@
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
<java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
<java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+ <java-symbol type="integer" name="config_longPressOnPowerDurationMs" />
+ <java-symbol type="array" name="config_longPressOnPowerDurationSettings" />
<java-symbol type="bool" name="config_longPressOnPowerForAssistantSettingAvailable" />
<java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
<java-symbol type="integer" name="config_veryLongPressTimeout" />
@@ -1742,6 +1744,7 @@
<java-symbol type="dimen" name="navigation_bar_width_car_mode" />
<java-symbol type="dimen" name="status_bar_height" />
<java-symbol type="dimen" name="display_cutout_touchable_region_size" />
+ <java-symbol type="dimen" name="system_gestures_start_threshold" />
<java-symbol type="dimen" name="quick_qs_offset_height" />
<java-symbol type="drawable" name="ic_jog_dial_sound_off" />
<java-symbol type="drawable" name="ic_jog_dial_sound_on" />
@@ -2524,6 +2527,7 @@
<java-symbol type="string" name="fingerprint_error_no_space" />
<java-symbol type="string" name="fingerprint_error_timeout" />
<java-symbol type="array" name="fingerprint_error_vendor" />
+ <java-symbol type="string" name="fingerprint_error_vendor_unknown" />
<java-symbol type="string" name="fingerprint_acquired_partial" />
<java-symbol type="string" name="fingerprint_acquired_insufficient" />
<java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
@@ -2563,6 +2567,7 @@
<java-symbol type="string" name="face_error_no_space" />
<java-symbol type="string" name="face_error_timeout" />
<java-symbol type="array" name="face_error_vendor" />
+ <java-symbol type="string" name="face_error_vendor_unknown" />
<java-symbol type="string" name="face_error_canceled" />
<java-symbol type="string" name="face_error_user_canceled" />
<java-symbol type="string" name="face_error_lockout" />
@@ -2605,6 +2610,7 @@
<java-symbol type="array" name="config_udfps_sensor_props" />
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
+ <java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
@@ -3184,6 +3190,7 @@
<java-symbol type="id" name="notification_action_list_margin_target" />
<java-symbol type="dimen" name="notification_actions_padding_start"/>
+ <java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/>
<java-symbol type="dimen" name="notification_action_disabled_alpha" />
<java-symbol type="id" name="tag_margin_end_when_icon_visible" />
<java-symbol type="id" name="tag_margin_end_when_icon_gone" />
@@ -3949,6 +3956,8 @@
<java-symbol type="integer" name="config_defaultRefreshRateInZone" />
<java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
<java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
+ <java-symbol type="integer" name="config_defaultRefreshRateInHbmSunlight" />
+ <java-symbol type="integer" name="config_defaultRefreshRateInHbmHdr" />
<!-- For fixed refresh rate displays in high brightness-->
<java-symbol type="integer" name="config_fixedRefreshRateInHighZone" />
@@ -4431,6 +4440,8 @@
<java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
+ <java-symbol type="color" name="overview_background"/>
+
<java-symbol type="bool" name="config_disableTaskSnapshots" />
</resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index cd07d46..34c1763 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,9 +16,11 @@
package android.app;
-import static androidx.core.graphics.ColorUtils.calculateContrast;
+import static android.app.Notification.Builder.ensureColorSpanContrast;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsAtLeast;
+import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsWithinRange;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +37,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@@ -42,12 +45,21 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.TextAppearanceSpan;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+import com.android.internal.util.ContrastColorUtil;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -334,6 +346,163 @@
}
@Test
+ public void testBuilder_getFullLengthSpanColor_returnsNullForString() {
+ assertThat(Notification.Builder.getFullLengthSpanColor("String")).isNull();
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_returnsNullWithPartialSpan() {
+ CharSequence text = new SpannableStringBuilder()
+ .append("text with ")
+ .append("some red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isNull();
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithSingleSpan() {
+ CharSequence text = new SpannableStringBuilder()
+ .append("text that is all red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.RED);
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithFullAndPartialSpans() {
+ Spannable text = new SpannableString("blue text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.BLUE);
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithTextAppearance() {
+ Spannable text = new SpannableString("title text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ int expectedTextColor = textAppearanceSpan.getTextColor().getDefaultColor();
+ text.setSpan(textAppearanceSpan, 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(expectedTextColor);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_removesAllFullLengthColorSpans() {
+ Spannable text = new SpannableString("blue text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ TextAppearanceSpan taSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ assertThat(taSpan.getTextColor()).isNotNull(); // it must be set to prove it is cleared.
+ text.setSpan(taSpan, 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spannable result = (Spannable) ensureColorSpanContrast(text, Color.BLACK);
+ Object[] spans = result.getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(3);
+
+ assertThat(result.getSpanStart(spans[0])).isEqualTo(15);
+ assertThat(result.getSpanEnd(spans[0])).isEqualTo(21);
+ assertThat(((ForegroundColorSpan) spans[0]).getForegroundColor()).isEqualTo(Color.YELLOW);
+
+ assertThat(result.getSpanStart(spans[1])).isEqualTo(0);
+ assertThat(result.getSpanEnd(spans[1])).isEqualTo(31);
+ assertThat(spans[1]).isNotSameInstanceAs(taSpan); // don't mutate the existing span
+ assertThat(((TextAppearanceSpan) spans[1]).getFamily()).isEqualTo(taSpan.getFamily());
+ assertThat(((TextAppearanceSpan) spans[1]).getTextColor()).isNull();
+
+ assertThat(result.getSpanStart(spans[2])).isEqualTo(26);
+ assertThat(result.getSpanEnd(spans[2])).isEqualTo(31);
+ assertThat(((ForegroundColorSpan) spans[2]).getForegroundColor()).isEqualTo(Color.GREEN);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_partialLength_adjusted() {
+ int background = 0xFFFF0101; // Slightly lighter red
+ CharSequence text = new SpannableStringBuilder()
+ .append("text with ")
+ .append("some red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ CharSequence result = ensureColorSpanContrast(text, background);
+
+ // ensure the span has been updated to have > 1.3:1 contrast ratio with fill color
+ Object[] spans = ((Spannable) result).getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(1);
+ int foregroundColor = ((ForegroundColorSpan) spans[0]).getForegroundColor();
+ assertContrastIsWithinRange(foregroundColor, background, 3, 3.2);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_worksWithComplexInput() {
+ Spannable text = new SpannableString("blue text with yellow and green and cyan");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // cyan TextAppearanceSpan
+ TextAppearanceSpan taSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ taSpan = new TextAppearanceSpan(taSpan.getFamily(), taSpan.getTextStyle(),
+ taSpan.getTextSize(), ColorStateList.valueOf(Color.CYAN), null);
+ text.setSpan(taSpan, 36, 40,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spannable result = (Spannable) ensureColorSpanContrast(text, Color.GRAY);
+ Object[] spans = result.getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(3);
+
+ assertThat(result.getSpanStart(spans[0])).isEqualTo(15);
+ assertThat(result.getSpanEnd(spans[0])).isEqualTo(21);
+ assertThat(((ForegroundColorSpan) spans[0]).getForegroundColor()).isEqualTo(Color.YELLOW);
+
+ assertThat(result.getSpanStart(spans[1])).isEqualTo(36);
+ assertThat(result.getSpanEnd(spans[1])).isEqualTo(40);
+ assertThat(spans[1]).isNotSameInstanceAs(taSpan); // don't mutate the existing span
+ assertThat(((TextAppearanceSpan) spans[1]).getFamily()).isEqualTo(taSpan.getFamily());
+ ColorStateList newCyanList = ((TextAppearanceSpan) spans[1]).getTextColor();
+ assertThat(newCyanList).isNotNull();
+ assertContrastIsWithinRange(newCyanList.getDefaultColor(), Color.GRAY, 3, 3.2);
+
+ assertThat(result.getSpanStart(spans[2])).isEqualTo(26);
+ assertThat(result.getSpanEnd(spans[2])).isEqualTo(31);
+ int newGreen = ((ForegroundColorSpan) spans[2]).getForegroundColor();
+ assertThat(newGreen).isNotEqualTo(Color.GREEN);
+ assertContrastIsWithinRange(newGreen, Color.GRAY, 3, 3.2);
+ }
+
+ @Test
+ public void testBuilder_ensureButtonFillContrast_adjustsDarker() {
+ int background = Color.LTGRAY;
+ int foreground = Color.LTGRAY;
+ int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
+ assertContrastIsWithinRange(result, background, 1.3, 1.5);
+ assertThat(ContrastColorUtil.calculateLuminance(result))
+ .isLessThan(ContrastColorUtil.calculateLuminance(background));
+ }
+
+ @Test
+ public void testBuilder_ensureButtonFillContrast_adjustsLighter() {
+ int background = Color.DKGRAY;
+ int foreground = Color.DKGRAY;
+ int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
+ assertContrastIsWithinRange(result, background, 1.3, 1.5);
+ assertThat(ContrastColorUtil.calculateLuminance(result))
+ .isGreaterThan(ContrastColorUtil.calculateLuminance(background));
+ }
+
+ @Test
public void testColors_ensureColors_dayMode_producesValidPalette() {
Notification.Colors c = new Notification.Colors();
boolean colorized = false;
@@ -399,6 +568,8 @@
assertEquals(cDay.getSecondaryTextColor(), cNight.getSecondaryTextColor());
assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor());
assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor());
+ assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor());
+ assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor());
assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor());
assertEquals(cDay.getContrastColor(), cNight.getContrastColor());
assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha());
@@ -413,30 +584,26 @@
assertThat(c.getSecondaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+ assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+ assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getRippleAlpha()).isAtLeast(0x00);
assertThat(c.getRippleAlpha()).isAtMost(0xff);
- // Assert that various colors have sufficient contrast
+ // Assert that various colors have sufficient contrast with the background
assertContrastIsAtLeast(c.getPrimaryTextColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getSecondaryTextColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getPrimaryAccentColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getErrorColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getContrastColor(), c.getBackgroundColor(), 4.5);
- // This accent color is only used for emphasized buttons
+ // These colors are only used for emphasized buttons; they do not need contrast
assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1);
- }
+ assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1);
- private void assertContrastIsAtLeast(int foreground, int background, double minContrast) {
- try {
- assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast);
- } catch (AssertionError e) {
- throw new AssertionError(
- String.format("Insufficient contrast: foreground=#%08x background=#%08x",
- foreground, background), e);
- }
+ // The text that is used within the accent color DOES need to have contrast
+ assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
}
private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor,
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 22f6ec0..701e619 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -27,6 +27,7 @@
import static junit.framework.Assert.fail;
+import android.graphics.fonts.FontCustomizationParser;
import android.graphics.fonts.FontStyle;
import android.os.LocaleList;
import android.text.FontConfig;
@@ -46,6 +47,7 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -318,6 +320,52 @@
}
}
+ @Test
+ public void alias() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + " <family name='custom-family'>"
+ + " <font>missing.ttf</font>"
+ + " </family>"
+ + " <alias name='custom-alias' to='sans-serif'/>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.Alias> aliases = config.getAliases();
+ assertThat(aliases.size()).isEqualTo(1);
+ assertThat(aliases.get(0).getName()).isEqualTo("custom-alias");
+ assertThat(aliases.get(0).getOriginal()).isEqualTo("sans-serif");
+ }
+
+ @Test
+ public void dropped_FamilyAlias() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + " <family name='custom-family'>"
+ + " <font>missing.ttf</font>"
+ + " </family>"
+ + " <alias name='custom-alias' to='custom-family'/>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, false /* exclude not existing file */);
+ assertThat(config.getAliases()).isEmpty();
+ }
+
+ private FontConfig readFamilies(String xml, boolean allowNonExisting)
+ throws IOException, XmlPullParserException {
+ ByteArrayInputStream buffer = new ByteArrayInputStream(
+ xml.getBytes(StandardCharsets.UTF_8));
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(buffer, "UTF-8");
+ parser.nextTag();
+ return FontListParser.readFamilies(parser, "", new FontCustomizationParser.Result(), null,
+ 0L /* last modified date */, 0 /* config version */, allowNonExisting);
+ }
+
private FontConfig.FontFamily readFamily(String xml)
throws IOException, XmlPullParserException {
ByteArrayInputStream buffer = new ByteArrayInputStream(
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index dfc9013..149f58f 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -32,6 +32,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -121,6 +122,46 @@
Mockito.verifyZeroInteractions(mListener);
}
+ @Test
+ public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreNoOtherListeners()
+ throws RemoteException {
+ mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+
+ }
+
+ @Test
+ public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
+ throws RemoteException {
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+ InOrder inOrder = Mockito.inOrder(mDisplayManager);
+
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(ALL_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.unregisterDisplayListener(mListener);
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+
+ }
+
private void waitForHandler() {
mHandler.runWithScissors(() -> { }, 0);
}
diff --git a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
index d936cad..3c8f90c 100644
--- a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
+++ b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
@@ -16,6 +16,10 @@
package android.net
+import android.net.NetworkTemplate.MATCH_BLUETOOTH
+import android.net.NetworkTemplate.MATCH_ETHERNET
+import android.net.NetworkTemplate.MATCH_MOBILE
+import android.net.NetworkTemplate.MATCH_WIFI
import android.text.format.Time.TIMEZONE_UTC
import androidx.test.runner.AndroidJUnit4
import org.junit.Test
@@ -24,16 +28,18 @@
import java.io.DataInputStream
import java.time.ZoneId
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
private const val TEST_IMSI1 = "TESTIMSI1"
-private const val TEST_SSID1 = "TESTISSID1"
+private const val TEST_WIFI_NETWORK_KEY1 = "TESTKEY1"
@RunWith(AndroidJUnit4::class)
class NetworkPolicyTest {
@Test
fun testTemplateBackupRestore() {
assertPolicyBackupRestore(createTestPolicyForTemplate(
- NetworkTemplate.buildTemplateWifi(TEST_SSID1)))
+ NetworkTemplate.buildTemplateWifi(TEST_WIFI_NETWORK_KEY1)))
assertPolicyBackupRestore(createTestPolicyForTemplate(
NetworkTemplate.buildTemplateMobileAll(TEST_IMSI1)))
assertPolicyBackupRestore(createTestPolicyForTemplate(
@@ -53,4 +59,26 @@
val restored = NetworkPolicy.getNetworkPolicyFromBackup(stream)
assertEquals(policy, restored)
}
-}
\ No newline at end of file
+
+ @Test
+ fun testIsTemplatePersistable() {
+ listOf(MATCH_MOBILE, MATCH_WIFI).forEach {
+ // Verify wildcard templates cannot be persistable.
+ assertFalse(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it).build()))
+
+ // Verify mobile/wifi templates can be persistable if the Subscriber Id is supplied.
+ assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it)
+ .setSubscriberIds(setOf(TEST_IMSI1)).build()))
+ }
+
+ // Verify bluetooth and ethernet templates can be persistable without any other
+ // field is supplied.
+ listOf(MATCH_BLUETOOTH, MATCH_ETHERNET).forEach {
+ assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it).build()))
+ }
+
+ // Verify wifi template can be persistable if the Wifi Network Key is supplied.
+ assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(MATCH_WIFI)
+ .setWifiNetworkKeys(setOf(TEST_WIFI_NETWORK_KEY1)).build()))
+ }
+}
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index a5a98a9..109b7ab 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -17,6 +17,8 @@
package android.service.notification;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse;
@@ -51,6 +53,7 @@
private final Context mMockContext = mock(Context.class);
@Mock
+ private Context mRealContext;
private PackageManager mPm;
private static final String PKG = "com.example.o";
@@ -75,6 +78,8 @@
InstrumentationRegistry.getContext().getResources());
when(mMockContext.getPackageManager()).thenReturn(mPm);
when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+
+ mRealContext = InstrumentationRegistry.getContext();
}
@Test
@@ -199,6 +204,19 @@
}
+ @Test
+ public void testGetPackageContext_worksWithUserAll() {
+ String pkg = "com.android.systemui";
+ int uid = 1000;
+ Notification notification = getNotificationBuilder(GROUP_ID_1, CHANNEL_ID).build();
+ StatusBarNotification sbn = new StatusBarNotification(
+ pkg, pkg, ID, TAG, uid, uid, notification, UserHandle.ALL, null, UID);
+ Context resultContext = sbn.getPackageContext(mRealContext);
+ assertNotNull(resultContext);
+ assertNotSame(mRealContext, resultContext);
+ assertEquals(pkg, resultContext.getPackageName());
+ }
+
private StatusBarNotification getNotification(String pkg, String group, String channelId) {
return getNotification(pkg, getNotificationBuilder(group, channelId));
}
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 020f4a0..073e468 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -23,11 +23,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.content.res.Configuration;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.IWindowManager;
@@ -38,6 +40,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
/**
* Tests for {@link WindowContextController}
@@ -53,15 +57,18 @@
@Presubmit
public class WindowContextControllerTest {
private WindowContextController mController;
+ @Mock
private IWindowManager mMockWms;
+ @Mock
+ private WindowTokenClient mMockToken;
@Before
public void setUp() throws Exception {
- mMockWms = mock(IWindowManager.class);
- mController = new WindowContextController(new Binder(), mMockWms);
-
- doReturn(true).when(mMockWms).attachWindowContextToDisplayArea(any(), anyInt(),
- anyInt(), any());
+ MockitoAnnotations.initMocks(this);
+ mController = new WindowContextController(mMockToken, mMockWms);
+ doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt());
+ doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(),
+ anyInt(), anyInt(), any());
}
@Test(expected = IllegalStateException.class)
@@ -85,6 +92,7 @@
null /* options */);
assertThat(mController.mAttachedToDisplayArea).isTrue();
+ verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY));
mController.detachIfNeeded();
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 464412f..d4799a8 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -23,6 +23,8 @@
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.Uid.Sensor;
+import android.os.Process;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.util.SparseLongArray;
import android.view.Display;
@@ -53,6 +55,8 @@
public class BatteryStatsNoteTest extends TestCase {
private static final int UID = 10500;
+ private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
+ private static final int ISOLATED_UID = UserHandle.getUid(0, ISOLATED_APP_ID);
private static final WorkSource WS = new WorkSource(UID);
/**
@@ -114,6 +118,88 @@
assertEquals(120_000, bgTime);
}
+ /**
+ * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid.
+ */
+ @SmallTest
+ public void testNoteStartWakeLocked_isolatedUid() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ int pid = 10;
+ String name = "name";
+ String historyName = "historyName";
+
+ WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+ isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+ // Map ISOLATED_UID to UID.
+ bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+ bi.noteStartWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL, false);
+
+ clocks.realtime = clocks.uptime = 100;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ clocks.realtime = clocks.uptime = 220;
+ bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL);
+
+ // ISOLATED_UID wakelock time should be attributed to UID.
+ BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+ .getAggregatedPartialWakelockTimer();
+ long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ assertEquals(220_000, actualTime);
+ assertEquals(120_000, bgTime);
+ }
+
+ /**
+ * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid, with a race where the
+ * isolated uid is removed from batterystats before the wakelock has been stopped.
+ */
+ @SmallTest
+ public void testNoteStartWakeLocked_isolatedUidRace() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ int pid = 10;
+ String name = "name";
+ String historyName = "historyName";
+
+ WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+ isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+ // Map ISOLATED_UID to UID.
+ bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+ bi.noteStartWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL, false);
+
+ clocks.realtime = clocks.uptime = 100;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ clocks.realtime = clocks.uptime = 150;
+ bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime);
+
+ clocks.realtime = clocks.uptime = 220;
+ bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL);
+
+ // ISOLATED_UID wakelock time should be attributed to UID.
+ BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+ .getAggregatedPartialWakelockTimer();
+ long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ assertEquals(220_000, actualTime);
+ assertEquals(120_000, bgTime);
+ }
+
/**
* Test BatteryStatsImpl.noteUidProcessStateLocked.
@@ -506,7 +592,7 @@
public void testUpdateDisplayMeasuredEnergyStatsLocked() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
clocks.realtime = 0;
int screen = Display.STATE_OFF;
@@ -591,7 +677,7 @@
public void testUpdateCustomMeasuredEnergyStatsLocked_neverCalled() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
bi.setOnBatteryInternal(true);
final int uid1 = 11500;
@@ -605,7 +691,7 @@
public void testUpdateCustomMeasuredEnergyStatsLocked() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
final int bucketA = 0; // Custom bucket 0
final int bucketB = 1; // Custom bucket 1
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index b851f0a..0135fe8 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -403,7 +403,7 @@
assertNotNull(sensor.getSensorBackgroundTime());
// Reset the stats. Since the sensor is still running, we should still see the timer
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
sensor = uid.getSensorStats().get(SENSOR_ID);
assertNotNull(sensor);
@@ -413,7 +413,7 @@
bi.noteStopSensorLocked(UID, SENSOR_ID);
// Now the sensor timer has stopped so this reset should also take out the sensor.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
sensor = uid.getSensorStats().get(SENSOR_ID);
assertNull(sensor);
@@ -465,7 +465,7 @@
// Reset the stats. Since the sensor is still running, we should still see the timer
// but still with 0 times.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
assertEquals(0, timer.getTotalTimeLocked(1000*clocks.realtime, which));
assertEquals(0, timer.getTotalDurationMsLocked(clocks.realtime));
assertEquals(0, bgTimer.getTotalTimeLocked(1000*clocks.realtime, which));
@@ -504,7 +504,7 @@
// Reset the stats. Since the sensor is still running, we should still see the timer
// but with 0 times.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
assertEquals(0, timer.getTotalTimeLocked(1000*clocks.realtime, which));
assertEquals(0, timer.getTotalDurationMsLocked(clocks.realtime));
assertEquals(0, bgTimer.getTotalTimeLocked(1000*clocks.realtime, which));
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 0147cdb..74b6dbe 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -18,6 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.app.ActivityManager;
import android.content.Context;
import android.os.BatteryConsumer;
@@ -263,6 +266,39 @@
.of(180.0);
}
+ @Test
+ public void testAggregateBatteryStats_incompatibleSnapshot() {
+ Context context = InstrumentationRegistry.getContext();
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
+
+ BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class);
+
+ when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps())
+ .thenReturn(new long[]{1000, 2000});
+
+ when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn(
+ new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
+ .setStatsDuration(1234).build());
+
+ // Add a snapshot, with a different set of custom power components. It should
+ // be skipped by the aggregation.
+ when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn(
+ new BatteryUsageStats.Builder(new String[]{"different"})
+ .setStatsDuration(4321).build());
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
+ batteryStats, batteryUsageStatsStore);
+
+ BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+ .aggregateSnapshots(0, 3000)
+ .build();
+ final BatteryUsageStats stats = provider.getBatteryUsageStats(query);
+ assertThat(stats.getCustomPowerComponentNames())
+ .isEqualTo(batteryStats.getCustomEnergyConsumerNames());
+ assertThat(stats.getStatsDuration()).isEqualTo(1234);
+ }
+
private static class TestHandler extends Handler {
TestHandler() {
super(Looper.getMainLooper());
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 99d576d..cee1a03 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -57,11 +57,10 @@
this(new MockClocks());
}
- public void initMeasuredEnergyStats() {
+ public void initMeasuredEnergyStats(String[] customBucketNames) {
final boolean[] supportedStandardBuckets =
new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
Arrays.fill(supportedStandardBuckets, true);
- final String[] customBucketNames = {"FOO", "BAR"};
mGlobalMeasuredEnergyStats =
new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
}
diff --git a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
new file mode 100644
index 0000000..cfe660c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.util;
+
+import static androidx.core.graphics.ColorUtils.calculateContrast;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+
+import androidx.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+public class ContrastColorUtilTest extends TestCase {
+
+ @SmallTest
+ public void testEnsureTextContrastAgainstDark() {
+ int darkBg = 0xFF35302A;
+
+ int blueContrastColor = ContrastColorUtil.ensureTextContrast(Color.BLUE, darkBg, true);
+ assertContrastIsWithinRange(blueContrastColor, darkBg, 4.5, 4.75);
+
+ int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, darkBg, true);
+ assertContrastIsWithinRange(redContrastColor, darkBg, 4.5, 4.75);
+
+ final int darkGreen = 0xff008800;
+ int greenContrastColor = ContrastColorUtil.ensureTextContrast(darkGreen, darkBg, true);
+ assertContrastIsWithinRange(greenContrastColor, darkBg, 4.5, 4.75);
+
+ int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.DKGRAY, darkBg, true);
+ assertContrastIsWithinRange(grayContrastColor, darkBg, 4.5, 4.75);
+
+ int selfContrastColor = ContrastColorUtil.ensureTextContrast(darkBg, darkBg, true);
+ assertContrastIsWithinRange(selfContrastColor, darkBg, 4.5, 4.75);
+ }
+
+ @SmallTest
+ public void testEnsureTextContrastAgainstLight() {
+ int lightBg = 0xFFFFF8F2;
+
+ final int lightBlue = 0xff8888ff;
+ int blueContrastColor = ContrastColorUtil.ensureTextContrast(lightBlue, lightBg, false);
+ assertContrastIsWithinRange(blueContrastColor, lightBg, 4.5, 4.75);
+
+ int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, lightBg, false);
+ assertContrastIsWithinRange(redContrastColor, lightBg, 4.5, 4.75);
+
+ int greenContrastColor = ContrastColorUtil.ensureTextContrast(Color.GREEN, lightBg, false);
+ assertContrastIsWithinRange(greenContrastColor, lightBg, 4.5, 4.75);
+
+ int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.LTGRAY, lightBg, false);
+ assertContrastIsWithinRange(grayContrastColor, lightBg, 4.5, 4.75);
+
+ int selfContrastColor = ContrastColorUtil.ensureTextContrast(lightBg, lightBg, false);
+ assertContrastIsWithinRange(selfContrastColor, lightBg, 4.5, 4.75);
+ }
+
+ public static void assertContrastIsWithinRange(int foreground, int background,
+ double minContrast, double maxContrast) {
+ assertContrastIsAtLeast(foreground, background, minContrast);
+ assertContrastIsAtMost(foreground, background, maxContrast);
+ }
+
+ public static void assertContrastIsAtLeast(int foreground, int background, double minContrast) {
+ try {
+ assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast);
+ } catch (AssertionError e) {
+ throw new AssertionError(
+ String.format("Insufficient contrast: foreground=#%08x background=#%08x",
+ foreground, background), e);
+ }
+ }
+
+ public static void assertContrastIsAtMost(int foreground, int background, double maxContrast) {
+ try {
+ assertThat(calculateContrast(foreground, background)).isAtMost(maxContrast);
+ } catch (AssertionError e) {
+ throw new AssertionError(
+ String.format("Excessive contrast: foreground=#%08x background=#%08x",
+ foreground, background), e);
+ }
+ }
+
+}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea23aba..2c93d5a 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -8,8 +8,6 @@
lorenzo@google.com
svetoslavganov@android.com
svetoslavganov@google.com
-toddke@android.com
-toddke@google.com
patb@google.com
yamasani@google.com
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 40dda65..eeb65ae 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -48,6 +48,7 @@
<permission name="android.permission.SET_ACTIVITY_WATCHER"/>
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.car.permission.CONTROL_APP_BLOCKING"/>
<!-- use for rotary fragment to enable/disable packages related to rotary -->
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 1c7659e..fefcebe 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -393,6 +393,8 @@
<!-- Permissions required for Incremental CTS tests -->
<permission name="com.android.permission.USE_INSTALLER_V2"/>
<permission name="android.permission.LOADER_USAGE_STATS"/>
+ <!-- Permissions required for Package Verifier tests -->
+ <permission name="android.permission.PACKAGE_VERIFICATION_AGENT" />
<!-- Permission required to test system only camera devices. -->
<permission name="android.permission.SYSTEM_CAMERA" />
<!-- Permission required to test ExplicitHealthCheckServiceImpl. -->
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 93a336e..96b3325 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -25,6 +25,7 @@
import android.os.Build;
import android.os.LocaleList;
import android.text.FontConfig;
+import android.util.ArraySet;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -37,6 +38,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -120,7 +122,23 @@
}
}
- private static FontConfig readFamilies(
+ /**
+ * Parses the familyset tag in font.xml
+ * @param parser a XML pull parser
+ * @param fontDir A system font directory, e.g. "/system/fonts"
+ * @param customization A OEM font customization
+ * @param updatableFontMap A map of updated font files
+ * @param lastModifiedDate A date that the system font is updated.
+ * @param configVersion A version of system font config.
+ * @param allowNonExistingFile true if allowing non-existing font files during parsing fonts.xml
+ * @return result of fonts.xml
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ *
+ * @hide
+ */
+ public static FontConfig readFamilies(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@NonNull FontCustomizationParser.Result customization,
@@ -159,7 +177,24 @@
}
families.addAll(oemNamedFamilies.values());
- return new FontConfig(families, aliases, lastModifiedDate, configVersion);
+
+ // Filters aliases that point to non-existing families.
+ Set<String> namedFamilies = new ArraySet<>();
+ for (int i = 0; i < families.size(); ++i) {
+ String name = families.get(i).getName();
+ if (name != null) {
+ namedFamilies.add(name);
+ }
+ }
+ List<FontConfig.Alias> filtered = new ArrayList<>();
+ for (int i = 0; i < aliases.size(); ++i) {
+ FontConfig.Alias alias = aliases.get(i);
+ if (namedFamilies.contains(alias.getOriginal())) {
+ filtered.add(alias);
+ }
+ }
+
+ return new FontConfig(families, filtered, lastModifiedDate, configVersion);
}
private static boolean keepReading(XmlPullParser parser)
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 5b79d76..9feb619 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -178,22 +178,8 @@
* <p>Android YUV P010 format.</p>
*
* P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
- * followed immediately by a Wx(H/2) CbCr plane. Each sample is
- * represented by a 16-bit little-endian value, with the lower 6 bits set
- * to zero.
- *
- * <p>This format assumes
- * <ul>
- * <li>an even height</li>
- * <li>a vertical stride equal to the height</li>
- * </ul>
- * </p>
- *
- * <pre> stride_in_bytes = stride * 2 </pre>
- * <pre> y_size = stride_in_bytes * height </pre>
- * <pre> cbcr_size = stride_in_bytes * (height / 2) </pre>
- * <pre> cb_offset = y_size </pre>
- * <pre> cr_offset = cb_offset + 2 </pre>
+ * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
+ * little-endian value, with the lower 6 bits set to zero.
*
* <p>For example, the {@link android.media.Image} object can provide data
* in this format from a {@link android.hardware.camera2.CameraDevice}
@@ -841,7 +827,7 @@
case RAW_SENSOR:
return 16;
case YCBCR_P010:
- return 20;
+ return 24;
case RAW_DEPTH10:
case RAW10:
return 10;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index ba0ab6d..656bdff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -336,6 +336,13 @@
}
@Override
+ public void onImeDrawnOnTask(int taskId) {
+ if (mStartingWindow != null) {
+ mStartingWindow.onImeDrawnOnTask(taskId);
+ }
+ }
+
+ @Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (mLock) {
onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c0df06f..ac97c8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -576,20 +576,17 @@
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
- if (!mPositioner.showingInTaskbar()) {
- // Also, save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack(
- mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
- } else {
+ if (mPositioner.showingInTaskbar()) {
// In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
mMagnetizedObject = null;
+ } else {
+ // Save the magnetized stack so we can dispatch touch events to it.
+ mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
+ mMagnetizedObject.clearAllTargets();
+ mMagnetizedObject.addTarget(mMagneticTarget);
+ mMagnetizedObject.setMagnetListener(mStackMagnetListener);
}
- // Also, save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-
mIsDraggingStack = true;
// Cancel animations to make the stack temporarily invisible, since we're now
@@ -881,7 +878,6 @@
mRelativeStackPositionBeforeRotation = null;
}
- setUpDismissView();
if (mIsExpanded) {
// Re-draw bubble row and pointer for new orientation.
beforeExpandedViewAnimation();
@@ -1043,10 +1039,9 @@
contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */);
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
- // MagnetizedObjects.
+ // MagnetizedObjects when the dismiss view gets shown.
mMagneticTarget = new MagnetizedObject.MagneticTarget(
mDismissView.getCircle(), dismissRadius);
-
mBubbleContainer.bringToFront();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 0802fb5..636e145 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -1024,11 +1024,9 @@
}
/**
- * Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided
- * {@link MagnetizedObject.MagneticTarget} added as a target.
+ * Returns the {@link MagnetizedObject} instance for the bubble stack.
*/
- public MagnetizedObject<StackAnimationController> getMagnetizedStack(
- MagnetizedObject.MagneticTarget target) {
+ public MagnetizedObject<StackAnimationController> getMagnetizedStack() {
if (mMagnetizedStack == null) {
mMagnetizedStack = new MagnetizedObject<StackAnimationController>(
mLayout.getContext(),
@@ -1053,7 +1051,6 @@
loc[1] = (int) mStackPosition.y;
}
};
- mMagnetizedStack.addTarget(target);
mMagnetizedStack.setHapticsEnabled(true);
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index 9f6dd1f..9e01259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -303,6 +303,13 @@
}
/**
+ * Removes all associated targets from this object.
+ */
+ fun clearAllTargets() {
+ associatedTargets.clear()
+ }
+
+ /**
* Provide this method with all motion events that move the magnetized object. If the
* location of the motion events moves within the magnetic field of a target, or indicate a
* fling-to-target gesture, this method will return true and you should not move the object
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index d327470..9e1c61a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -23,6 +23,7 @@
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.view.ContextThemeWrapper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.animation.LinearInterpolator;
@@ -33,7 +34,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.view.ContextThemeWrapper;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 954ca14..e511bff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -445,6 +445,9 @@
mOneHandedSettingsUtil.registerSettingsKeyObserver(
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
}
private void unregisterSettingObservers() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 7cf4fb7..ff333c8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -171,9 +171,22 @@
* @return true if user enabled one-handed shortcut in settings, false otherwise.
*/
public boolean getShortcutEnabled(ContentResolver resolver, int userId) {
- final String targets = Settings.Secure.getStringForUser(resolver,
+ // Checks SOFTWARE_SHORTCUT_KEY
+ final String targetsSwKey = Settings.Secure.getStringForUser(resolver,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
- return TextUtils.isEmpty(targets) ? false : targets.contains(ONE_HANDED_MODE_TARGET_NAME);
+ if (!TextUtils.isEmpty(targetsSwKey) && targetsSwKey.contains(
+ ONE_HANDED_MODE_TARGET_NAME)) {
+ return true;
+ }
+
+ // Checks HARDWARE_SHORTCUT_KEY
+ final String targetsHwKey = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+ if (!TextUtils.isEmpty(targetsHwKey) && targetsHwKey.contains(
+ ONE_HANDED_MODE_TARGET_NAME)) {
+ return true;
+ }
+ return false;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index f58c6b1..81dd60d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -32,6 +32,7 @@
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -44,7 +45,6 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.appcompat.view.ContextThemeWrapper;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4f3ec96..63f1985 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -303,13 +303,13 @@
mOneHandedController = oneHandedController;
mPipTransitionController = pipTransitionController;
mTaskStackListener = taskStackListener;
- mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
- INPUT_CONSUMER_PIP, mainExecutor);
//TODO: move this to ShellInit when PipController can be injected
mMainExecutor.execute(this::init);
}
public void init() {
+ mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
+ INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipBoundsState.setDisplayId(displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
index a5e96d1..20021eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -28,6 +28,7 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
+import android.util.Log;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -45,7 +46,7 @@
class SizeCompatUILayout {
private static final String TAG = "SizeCompatUILayout";
- private final SyncTransactionQueue mSyncQueue;
+ final SyncTransactionQueue mSyncQueue;
private final SizeCompatUIController.SizeCompatUICallback mCallback;
private Context mContext;
private Configuration mTaskConfig;
@@ -306,6 +307,10 @@
private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) {
mSyncQueue.runInSync(t -> {
+ if (!leash.isValid()) {
+ Log.w(TAG, "The leash has been released.");
+ return;
+ }
t.setPosition(leash, positionX, positionY);
// The size compat UI should be the topmost child of the Task in case there can be more
// than one children.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
index f634c45..82f69c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
@@ -110,7 +110,8 @@
}
if (mLeash != null) {
- new SurfaceControl.Transaction().remove(mLeash).apply();
+ final SurfaceControl leash = mLeash;
+ mLayout.mSyncQueue.runInSync(t -> t.remove(leash));
mLeash = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index fc7c86d..147f5e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -545,6 +545,15 @@
removeWindowSynced(taskId, null, null, false);
}
+ void onImeDrawnOnTask(int taskId) {
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null && record.mTaskSnapshotWindow != null
+ && record.mTaskSnapshotWindow.hasImeSurface()) {
+ record.mTaskSnapshotWindow.removeImmediately();
+ }
+ mStartingWindowRecords.remove(taskId);
+ }
+
protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame,
boolean playRevealAnimation) {
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
@@ -572,14 +581,15 @@
Slog.e(TAG, "Found empty splash screen, remove!");
removeWindowInner(record.mDecorView, false);
}
+ mStartingWindowRecords.remove(taskId);
}
if (record.mTaskSnapshotWindow != null) {
if (DEBUG_TASK_SNAPSHOT) {
Slog.v(TAG, "Removing task snapshot window for " + taskId);
}
- record.mTaskSnapshotWindow.remove();
+ record.mTaskSnapshotWindow.scheduleRemove(
+ () -> mStartingWindowRecords.remove(taskId));
}
- mStartingWindowRecords.remove(taskId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index e84d498..dee21b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -177,6 +177,13 @@
}
/**
+ * Called when the IME has drawn on the organized task.
+ */
+ public void onImeDrawnOnTask(int taskId) {
+ mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId));
+ }
+
+ /**
* Called when the content of a task is ready to show, starting window can be removed.
*/
public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 6052d3d..dfb1ae3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -64,7 +64,6 @@
import android.hardware.HardwareBuffer;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.util.MergedConfiguration;
import android.util.Slog;
@@ -119,7 +118,12 @@
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
- private static final long DELAY_REMOVAL_TIME_IME_VISIBLE = 350;
+ /**
+ * The max delay time in milliseconds for removing the task snapshot window with IME visible.
+ * Ideally the delay time will be shorter when receiving
+ * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+ */
+ private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
//tmp vars for unused relayout params
private static final Point TMP_SURFACE_SIZE = new Point();
@@ -138,7 +142,6 @@
private final RectF mTmpDstFrame = new RectF();
private final CharSequence mTitle;
private boolean mHasDrawn;
- private long mShownTime;
private boolean mSizeMismatch;
private final Paint mBackgroundPaint = new Paint();
private final int mActivityType;
@@ -148,6 +151,8 @@
private final SurfaceControl.Transaction mTransaction;
private final Matrix mSnapshotMatrix = new Matrix();
private final float[] mTmpFloat9 = new float[9];
+ private Runnable mScheduledRunnable;
+ private final boolean mHasImeSurface;
static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
TaskSnapshot snapshot, ShellExecutor splashScreenExecutor,
@@ -216,7 +221,7 @@
taskDescription.setBackgroundColor(WHITE);
}
- final long delayRemovalTime = snapshot.hasImeSurface() ? DELAY_REMOVAL_TIME_IME_VISIBLE
+ final long delayRemovalTime = snapshot.hasImeSurface() ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
: DELAY_REMOVAL_TIME_GENERAL;
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
@@ -281,12 +286,17 @@
mDelayRemovalTime = delayRemovalTime;
mTransaction = new SurfaceControl.Transaction();
mClearWindowHandler = clearWindowHandler;
+ mHasImeSurface = snapshot.hasImeSurface();
}
int getBackgroundColor() {
return mBackgroundPaint.getColor();
}
+ boolean hasImeSurface() {
+ return mHasImeSurface;
+ }
+
/**
* Ask system bar background painter to draw status bar background.
* @hide
@@ -304,21 +314,32 @@
mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
}
- void remove() {
- final long now = SystemClock.uptimeMillis();
- if ((now - mShownTime < mDelayRemovalTime)
- // Show the latest content as soon as possible for unlocking to home.
- && mActivityType != ACTIVITY_TYPE_HOME) {
- final long delayTime = mShownTime + mDelayRemovalTime - now;
- mSplashScreenExecutor.executeDelayed(() -> remove(), delayTime);
- if (DEBUG) {
- Slog.d(TAG, "Defer removing snapshot surface in " + delayTime);
- }
+ void scheduleRemove(Runnable onRemove) {
+ // Show the latest content as soon as possible for unlocking to home.
+ if (mActivityType == ACTIVITY_TYPE_HOME) {
+ removeImmediately();
+ onRemove.run();
return;
}
+ if (mScheduledRunnable != null) {
+ mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
+ mScheduledRunnable = null;
+ }
+ mScheduledRunnable = () -> {
+ TaskSnapshotWindow.this.removeImmediately();
+ onRemove.run();
+ };
+ mSplashScreenExecutor.executeDelayed(mScheduledRunnable, mDelayRemovalTime);
+ if (DEBUG) {
+ Slog.d(TAG, "Defer removing snapshot surface in " + mDelayRemovalTime);
+ }
+ }
+
+ void removeImmediately() {
+ mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
try {
if (DEBUG) {
- Slog.d(TAG, "Removing snapshot surface, mHasDrawn: " + mHasDrawn);
+ Slog.d(TAG, "Removing taskSnapshot surface, mHasDrawn: " + mHasDrawn);
}
mSession.remove(mWindow);
} catch (RemoteException e) {
@@ -356,7 +377,6 @@
} else {
drawSizeMatchSnapshot();
}
- mShownTime = SystemClock.uptimeMillis();
mHasDrawn = true;
reportDrawn();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index d536adb..eef0d9b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -15,38 +15,53 @@
*/
package com.android.wm.shell.startingsurface;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.TestableContext;
+import android.view.IWindowSession;
+import android.view.InsetsState;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -61,6 +76,7 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
import java.util.function.IntSupplier;
@@ -78,6 +94,7 @@
private TransactionPool mTransactionPool;
private final Handler mTestHandler = new Handler(Looper.getMainLooper());
+ private ShellExecutor mTestExecutor;
private final TestableContext mTestContext = new TestContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
TestStartingSurfaceDrawer mStartingSurfaceDrawer;
@@ -138,9 +155,9 @@
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
doNothing().when(mMockWindowManager).addView(any(), any());
-
- mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(mTestContext,
- new HandlerExecutor(mTestHandler), mTransactionPool));
+ mTestExecutor = new HandlerExecutor(mTestHandler);
+ mStartingSurfaceDrawer = spy(
+ new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool));
}
@Test
@@ -205,6 +222,48 @@
assertEquals(0, windowColor3.mReuseCount);
}
+ @Test
+ public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
+ final int taskId = 1;
+ final StartingWindowInfo windowInfo =
+ createWindowInfo(taskId, android.R.style.Theme);
+ TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
+ new Rect(0, 0, 0, 50), true /* hasImeSurface */);
+ final IWindowSession session = WindowManagerGlobal.getWindowSession();
+ spyOn(session);
+ doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay(
+ any() /* window */, any() /* attrs */,
+ anyInt() /* viewVisibility */, anyInt() /* displayId */,
+ any() /* requestedVisibility */, any() /* outInputChannel */,
+ any() /* outInsetsState */, any() /* outActiveControls */);
+ TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
+ mBinder,
+ snapshot, mTestExecutor, () -> {
+ });
+ spyOn(mockSnapshotWindow);
+ try (AutoCloseable mockTaskSnapshotSession = new AutoCloseable() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(TaskSnapshotWindow.class)
+ .startMocking();
+ @Override
+ public void close() {
+ mockSession.finishMocking();
+ }
+ }) {
+ when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
+ any())).thenReturn(mockSnapshotWindow);
+ // Simulate a task snapshot window created with IME snapshot shown.
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
+ waitHandlerIdle(mTestHandler);
+
+ // Verify the task snapshot with IME snapshot will be removed when received the real IME
+ // drawn callback.
+ mStartingSurfaceDrawer.onImeDrawnOnTask(1);
+ verify(mockSnapshotWindow).removeImmediately();
+ }
+ }
+
private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
StartingWindowInfo windowInfo = new StartingWindowInfo();
final ActivityInfo info = new ActivityInfo();
@@ -216,10 +275,27 @@
taskInfo.taskId = taskId;
windowInfo.targetActivityInfo = info;
windowInfo.taskInfo = taskInfo;
+ windowInfo.topOpaqueWindowInsetsState = new InsetsState();
+ windowInfo.mainWindowLayoutParams = new WindowManager.LayoutParams();
+ windowInfo.topOpaqueWindowLayoutParams = new WindowManager.LayoutParams();
return windowInfo;
}
private static void waitHandlerIdle(Handler handler) {
handler.runWithScissors(() -> { }, 0 /* timeout */);
}
+
+ private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
+ Rect contentInsets, boolean hasImeSurface) {
+ final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
+ 1, HardwareBuffer.USAGE_CPU_READ_RARELY);
+ return new TaskSnapshot(
+ System.currentTimeMillis(),
+ new ComponentName("", ""), buffer,
+ ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+ Surface.ROTATION_0, taskSize, contentInsets, false,
+ true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
+ 0 /* systemUiVisibility */, false /* isTranslucent */,
+ hasImeSurface /* hasImeSurface */);
+ }
}
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 17f5164..38b6425 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,5 +1,4 @@
set noparent
-toddke@google.com
zyy@google.com
patb@google.com
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index c804418..513ad9a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -50,6 +50,7 @@
bool Properties::skipEmptyFrames = true;
bool Properties::useBufferAge = true;
bool Properties::enablePartialUpdates = true;
+bool Properties::enableRenderEffectCache = false;
DebugLevel Properties::debugLevel = kDebugDisabled;
OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -84,7 +85,7 @@
bool Properties::useHintManager = true;
int Properties::targetCpuTimePercentage = 70;
-bool Properties::enableWebViewOverlays = false;
+bool Properties::enableWebViewOverlays = true;
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
@@ -141,7 +142,7 @@
targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70);
if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
- enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, false);
+ enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true);
// call isDrawingEnabled to force loading of the property
isDrawingEnabled();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 7f9782b..2f8c679 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -232,6 +232,7 @@
static bool skipEmptyFrames;
static bool useBufferAge;
static bool enablePartialUpdates;
+ static bool enableRenderEffectCache;
// TODO: Move somewhere else?
static constexpr float textGamma = 1.45f;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 0c422df..b348a6e 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -341,6 +341,7 @@
sk_sp<SkImage> snapshot = layerSurface->makeImageSnapshot();
const auto subset = SkIRect::MakeWH(properties().getWidth(),
properties().getHeight());
+ uint32_t layerSurfaceGenerationId = layerSurface->generationID();
// If we don't have an ImageFilter just return the snapshot
if (imageFilter == nullptr) {
mSnapshotResult.snapshot = snapshot;
@@ -348,9 +349,10 @@
mSnapshotResult.outOffset = SkIPoint::Make(0.0f, 0.0f);
mImageFilterClipBounds = clipBounds;
mTargetImageFilter = nullptr;
- } else if (mSnapshotResult.snapshot == nullptr ||
- imageFilter != mTargetImageFilter.get() ||
- mImageFilterClipBounds != clipBounds) {
+ mTargetImageFilterLayerSurfaceGenerationId = 0;
+ } else if (mSnapshotResult.snapshot == nullptr || imageFilter != mTargetImageFilter.get() ||
+ mImageFilterClipBounds != clipBounds ||
+ mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) {
// Otherwise create a new snapshot with the given filter and snapshot
mSnapshotResult.snapshot =
snapshot->makeWithFilter(context,
@@ -361,6 +363,7 @@
&mSnapshotResult.outOffset);
mTargetImageFilter = sk_ref_sp(imageFilter);
mImageFilterClipBounds = clipBounds;
+ mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId;
}
return mSnapshotResult;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 45a4f6c..da04762 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -396,6 +396,7 @@
* SkImageFilter used to create the mSnapshotResult
*/
sk_sp<SkImageFilter> mTargetImageFilter;
+ uint32_t mTargetImageFilterLayerSurfaceGenerationId = 0;
/**
* Clip bounds used to create the mSnapshotResult
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index df41011..5aad821 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -100,6 +100,9 @@
destroyContext();
ATRACE_NAME("WebViewFunctor::onDestroy");
+ if (mSurfaceControl) {
+ removeOverlays();
+ }
mCallbacks.onDestroyed(mFunctor, mData);
}
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 7556af9..48145d2 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -231,14 +231,33 @@
SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
SkPaint paint;
layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
- const auto snapshotResult = renderNode->updateSnapshotIfRequired(
- canvas->recordingContext(),
- layerProperties.getImageFilter(),
- clipBounds.roundOut()
- );
- sk_sp<SkImage> snapshotImage = snapshotResult->snapshot;
- srcBounds = snapshotResult->outSubset;
- offset = snapshotResult->outOffset;
+ sk_sp<SkImage> snapshotImage;
+ auto* imageFilter = layerProperties.getImageFilter();
+ auto recordingContext = canvas->recordingContext();
+ // On some GL vendor implementations, caching the result of
+ // getLayerSurface->makeImageSnapshot() causes a call to
+ // Fence::waitForever without a corresponding signal. This would
+ // lead to ANRs throughout the system.
+ // Instead only cache the SkImage created with the SkImageFilter
+ // for supported devices. Otherwise just create a new SkImage with
+ // the corresponding SkImageFilter each time.
+ // See b/193145089 and b/197263715
+ if (!Properties::enableRenderEffectCache) {
+ snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
+ if (imageFilter) {
+ auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height());
+ snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter,
+ subset, clipBounds.roundOut(),
+ &srcBounds, &offset);
+ }
+ } else {
+ const auto snapshotResult = renderNode->updateSnapshotIfRequired(
+ recordingContext, layerProperties.getImageFilter(), clipBounds.roundOut());
+ snapshotImage = snapshotResult->snapshot;
+ srcBounds = snapshotResult->outSubset;
+ offset = snapshotResult->outOffset;
+ }
+
const auto dstBounds = SkIRect::MakeXYWH(offset.x(),
offset.y(),
srcBounds.width(),
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index a116781..383c79b 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -146,6 +146,9 @@
LOG_ALWAYS_FATAL("Unsupported wide color space.");
}
mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension;
+
+ auto* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
+ Properties::enableRenderEffectCache = (strcmp(vendor, "Qualcomm") != 0);
}
EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) {
diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java
index dc1e99f..2b978f7 100644
--- a/location/java/android/location/GpsNavigationMessage.java
+++ b/location/java/android/location/GpsNavigationMessage.java
@@ -262,12 +262,8 @@
parcel.readByteArray(data);
navigationMessage.setData(data);
- if (parcel.dataAvail() >= Integer.SIZE) {
- int status = parcel.readInt();
- navigationMessage.setStatus((short) status);
- } else {
- navigationMessage.setStatus(STATUS_UNKNOWN);
- }
+ int status = parcel.readInt();
+ navigationMessage.setStatus((short) status);
return navigationMessage;
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 60f4a5a..b7e8c8c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1172,7 +1172,8 @@
*
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
public int getLastAudibleStreamVolume(int streamType) {
final IAudioService service = getService();
try {
@@ -6791,56 +6792,63 @@
/**
* Returns a list of audio formats that corresponds to encoding formats
- * supported on offload path for A2DP and LE audio playback.
+ * supported on offload path for A2DP playback.
*
- * @param deviceType Indicates the target device type {@link AudioSystem.DeviceType}
* @return a list of {@link BluetoothCodecConfig} objects containing encoding formats
- * supported for offload A2DP playback or a list of {@link BluetoothLeAudioCodecConfig}
- * objects containing encoding formats supported for offload LE Audio playback
+ * supported for offload A2DP playback
* @hide
*/
- public List<?> getHwOffloadFormatsSupportedForBluetoothMedia(
- @AudioSystem.DeviceType int deviceType) {
- ArrayList<Integer> formatsList = new ArrayList<Integer>();
- ArrayList<BluetoothCodecConfig> a2dpCodecConfigList = new ArrayList<BluetoothCodecConfig>();
- ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList =
- new ArrayList<BluetoothLeAudioCodecConfig>();
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public @NonNull List<BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp() {
+ ArrayList<Integer> formatsList = new ArrayList<>();
+ ArrayList<BluetoothCodecConfig> codecConfigList = new ArrayList<>();
- if (deviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
- && deviceType != AudioSystem.DEVICE_OUT_BLE_HEADSET) {
- throw new IllegalArgumentException(
- "Illegal devicetype for the getHwOffloadFormatsSupportedForBluetoothMedia");
- }
-
- int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(deviceType,
- formatsList);
+ int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, formatsList);
if (status != AudioManager.SUCCESS) {
- Log.e(TAG, "getHwOffloadFormatsSupportedForBluetoothMedia for deviceType "
- + deviceType + " failed:" + status);
- return a2dpCodecConfigList;
+ Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForA2DP failed:" + status);
+ return codecConfigList;
}
- if (deviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
- for (Integer format : formatsList) {
- int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
- if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
- a2dpCodecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
- }
+ for (Integer format : formatsList) {
+ int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
+ if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+ codecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
}
- return a2dpCodecConfigList;
- } else if (deviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) {
- for (Integer format : formatsList) {
- int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format);
- if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
- leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder()
- .setCodecType(btLeAudioCodec)
- .build());
- }
- }
+ }
+ return codecConfigList;
+ }
+
+ /**
+ * Returns a list of audio formats that corresponds to encoding formats
+ * supported on offload path for Le audio playback.
+ *
+ * @return a list of {@link BluetoothLeAudioCodecConfig} objects containing encoding formats
+ * supported for offload Le Audio playback
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public List<BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio() {
+ ArrayList<Integer> formatsList = new ArrayList<>();
+ ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList = new ArrayList<>();
+
+ int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(
+ AudioSystem.DEVICE_OUT_BLE_HEADSET, formatsList);
+ if (status != AudioManager.SUCCESS) {
+ Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForLeAudio failed:" + status);
return leAudioCodecConfigList;
}
- Log.e(TAG, "Input deviceType " + deviceType + " doesn't support.");
- return a2dpCodecConfigList;
+
+ for (Integer format : formatsList) {
+ int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format);
+ if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+ leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder()
+ .setCodecType(btLeAudioCodec)
+ .build());
+ }
+ }
+ return leAudioCodecConfigList;
}
// Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the
diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl
index 6b754e1..9b49123 100644
--- a/media/java/android/media/IMediaRouterClient.aidl
+++ b/media/java/android/media/IMediaRouterClient.aidl
@@ -23,5 +23,4 @@
void onStateChanged();
void onRestoreRoute();
void onGroupRouteSelected(String routeId);
- void onGlobalA2dpChanged(boolean a2dpOn);
}
diff --git a/media/java/android/media/JetPlayer.java b/media/java/android/media/JetPlayer.java
index 875c6f5..7749564 100644
--- a/media/java/android/media/JetPlayer.java
+++ b/media/java/android/media/JetPlayer.java
@@ -31,35 +31,33 @@
/**
* JetPlayer provides access to JET content playback and control.
- *
- * <p>Please refer to the JET Creator User Manual for a presentation of the JET interactive
- * music concept and how to use the JetCreator tool to create content to be player by JetPlayer.
- *
+ *
+ * <p>Please refer to the
+ * <a href="https://developer.android.com/guide/topics/media/jet/jetcreator_manual">JET Creator User
+ * Manual</a> for a presentation of the JET interactive music concept and how to use the JetCreator
+ * tool to create content to be player by JetPlayer.
+ *
* <p>Use of the JetPlayer class is based around the playback of a number of JET segments
* sequentially added to a playback FIFO queue. The rendering of the MIDI content stored in each
* segment can be dynamically affected by two mechanisms:
* <ul>
- * <li>tracks in a segment can be muted or unmuted at any moment, individually or through
- * a mask (to change the mute state of multiple tracks at once)</li>
- * <li>parts of tracks in a segment can be played at predefined points in the segment, in order
- * to maintain synchronization with the other tracks in the segment. This is achieved through
- * the notion of "clips", which can be triggered at any time, but that will play only at the
- * right time, as authored in the corresponding JET file.</li>
+ * <li>Tracks in a segment can be muted or unmuted at any moment, individually or through a mask
+ * (to change the mute state of multiple tracks at once).
+ * <li>Parts of tracks in a segment can be played at predefined points in the segment, in order to
+ * maintain synchronization with the other tracks in the segment. This is achieved through the
+ * notion of "clips", which can be triggered at any time, but that will play only at the right
+ * time, as authored in the corresponding JET file.
* </ul>
- * As a result of the rendering and playback of the JET segments, the user of the JetPlayer instance
- * can receive notifications from the JET engine relative to:
- * <ul>
- * <li>the playback state,</li>
- * <li>the number of segments left to play in the queue,</li>
- * <li>application controller events (CC80-83) to mark points in the MIDI segments.</li>
- * </ul>
- * Use {@link #getJetPlayer()} to construct a JetPlayer instance. JetPlayer is a singleton class.
- * </p>
*
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For more information about how to use JetPlayer, read the
- * <a href="{@docRoot}guide/topics/media/jetplayer.html">JetPlayer</a> developer guide.</p></div>
+ * <p>As a result of the rendering and playback of the JET segments, the user of the JetPlayer
+ * instance can receive notifications from the JET engine relative to:
+ * <ul>
+ * <li>Playback state
+ * <li>Number of segments left to play in the queue
+ * <li>Application controller events (CC80-83) to mark points in the MIDI segments
+ * </ul>
+ *
+ * <p>Use {@link #getJetPlayer()} to construct a JetPlayer instance. JetPlayer is a singleton class.
*/
public class JetPlayer
{
@@ -140,7 +138,7 @@
//------------------------
/**
* Factory method for the JetPlayer class.
- * @return the singleton JetPlayer instance
+ * @return the singleton JetPlayer instance.
*/
public static JetPlayer getJetPlayer() {
if (singletonRef == null) {
@@ -203,7 +201,8 @@
// Getters
//------------------------
/**
- * Returns the maximum number of simultaneous MIDI tracks supported by JetPlayer
+ * Gets the maximum number of simultaneous MIDI tracks supported by JetPlayer.
+ * @return the maximum number of simultaneous MIDI tracks supported by JetPlayer.
*/
public static int getMaxTracks() {
return JetPlayer.MAXTRACKS;
@@ -459,10 +458,9 @@
//------------------------
/**
* Sets the listener JetPlayer notifies when a JET event is generated by the rendering and
- * playback engine.
- * Notifications will be received in the same thread as the one in which the JetPlayer
- * instance was created.
- * @param listener
+ * playback engine. Notifications are received in the same thread as the one in which the
+ * JetPlayer instance was created.
+ * @param listener the listener that will be notified when a JET event is generated.
*/
public void setEventListener(OnJetEventListener listener) {
setEventListener(listener, null);
@@ -470,10 +468,9 @@
/**
* Sets the listener JetPlayer notifies when a JET event is generated by the rendering and
- * playback engine.
- * Use this method to receive JET events in the Handler associated with another
- * thread than the one in which you created the JetPlayer instance.
- * @param listener
+ * playback engine. Use this method to receive JET events in the Handler associated with
+ * another thread than the one in which you created the JetPlayer instance.
+ * @param listener the listener that will be notified when a JET event is generated.
* @param handler the Handler that will receive the event notification messages.
*/
public void setEventListener(OnJetEventListener listener, Handler handler) {
diff --git a/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java
index dcd4dce..ec56d61 100644
--- a/media/java/android/media/MediaActionSound.java
+++ b/media/java/android/media/MediaActionSound.java
@@ -16,8 +16,11 @@
package android.media;
-import android.media.AudioManager;
+import android.content.Context;
import android.media.SoundPool;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
/**
@@ -104,6 +107,26 @@
private static final int STATE_LOADING_PLAY_REQUESTED = 2;
private static final int STATE_LOADED = 3;
+ /**
+ * <p>Returns true if the application must play the shutter sound in accordance
+ * to certain regional restrictions. </p>
+ *
+ * <p>If this method returns true, applications are strongly recommended to use
+ * MediaActionSound.play(SHUTTER_CLICK) or START_VIDEO_RECORDING whenever it captures
+ * images or video to storage or sends them over the network.</p>
+ */
+ public static boolean mustPlayShutterSound() {
+ boolean result = false;
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService audioService = IAudioService.Stub.asInterface(b);
+ try {
+ result = audioService.isCameraSoundForced();
+ } catch (RemoteException e) {
+ Log.e(TAG, "audio service is unavailable for queries, defaulting to false");
+ }
+ return result;
+ }
+
private class SoundState {
public final int name;
public int id;
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 345d9b2..2986f7c 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -654,9 +654,12 @@
final class Client extends IMediaRouterClient.Stub {
@Override
public void onStateChanged() {
- mHandler.post(() -> {
- if (Client.this == mClient) {
- updateClientState();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (Client.this == mClient) {
+ updateClientState();
+ }
}
});
}
@@ -690,26 +693,6 @@
}
});
}
-
- // Called when the selection of a connected device (phone speaker or BT devices)
- // is changed.
- @Override
- public void onGlobalA2dpChanged(boolean a2dpOn) {
- mHandler.post(() -> {
- if (mSelectedRoute == null || mBluetoothA2dpRoute == null) {
- return;
- }
- if (mSelectedRoute.isDefault() && a2dpOn) {
- setSelectedRoute(mBluetoothA2dpRoute, /*explicit=*/ false);
- dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
- dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
- } else if (mSelectedRoute.isBluetooth() && !a2dpOn) {
- setSelectedRoute(mDefaultAudioVideo, /*explicit=*/ false);
- dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
- dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
- }
- });
- }
}
}
@@ -1367,9 +1350,6 @@
}
static void dispatchRouteSelected(int type, RouteInfo info) {
- if (DEBUG) {
- Log.d(TAG, "Dispatching route selected: " + info);
- }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteSelected(cbi.router, type, info);
@@ -1378,9 +1358,6 @@
}
static void dispatchRouteUnselected(int type, RouteInfo info) {
- if (DEBUG) {
- Log.d(TAG, "Dispatching route unselected: " + info);
- }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteUnselected(cbi.router, type, info);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index d8d1ba13..a0827ac 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -45,6 +45,8 @@
TvInputInfo getTvInputInfo(in String inputId, int userId);
void updateTvInputInfo(in TvInputInfo inputInfo, int userId);
int getTvInputState(in String inputId, int userId);
+ List<String> getAvailableExtensionInterfaceNames(in String inputId, int userId);
+ IBinder getExtensionInterface(in String inputId, in String name, int userId);
List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId);
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
index 8ccf13a..64a23a2 100755
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -26,18 +26,21 @@
* Top-level interface to a TV input component (implemented in a Service).
* @hide
*/
-oneway interface ITvInputService {
- void registerCallback(in ITvInputServiceCallback callback);
- void unregisterCallback(in ITvInputServiceCallback callback);
- void createSession(in InputChannel channel, in ITvInputSessionCallback callback,
+interface ITvInputService {
+ oneway void registerCallback(in ITvInputServiceCallback callback);
+ oneway void unregisterCallback(in ITvInputServiceCallback callback);
+ oneway void createSession(in InputChannel channel, in ITvInputSessionCallback callback,
in String inputId, in String sessionId);
- void createRecordingSession(in ITvInputSessionCallback callback, in String inputId,
+ oneway void createRecordingSession(in ITvInputSessionCallback callback, in String inputId,
in String sessionId);
+ List<String> getAvailableExtensionInterfaceNames();
+ IBinder getExtensionInterface(in String name);
+ String getExtensionInterfacePermission(in String name);
// For hardware TvInputService
- void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo);
- void notifyHardwareRemoved(in TvInputHardwareInfo hardwareInfo);
- void notifyHdmiDeviceAdded(in HdmiDeviceInfo deviceInfo);
- void notifyHdmiDeviceRemoved(in HdmiDeviceInfo deviceInfo);
- void notifyHdmiDeviceUpdated(in HdmiDeviceInfo deviceInfo);
+ oneway void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo);
+ oneway void notifyHardwareRemoved(in TvInputHardwareInfo hardwareInfo);
+ oneway void notifyHdmiDeviceAdded(in HdmiDeviceInfo deviceInfo);
+ oneway void notifyHdmiDeviceRemoved(in HdmiDeviceInfo deviceInfo);
+ oneway void notifyHdmiDeviceUpdated(in HdmiDeviceInfo deviceInfo);
}
diff --git a/media/java/android/media/tv/OWNERS b/media/java/android/media/tv/OWNERS
index 8bccc9a..33acd0d 100644
--- a/media/java/android/media/tv/OWNERS
+++ b/media/java/android/media/tv/OWNERS
@@ -1,5 +1,6 @@
nchalko@google.com
quxiangfang@google.com
+shubang@google.com
# For android remote service
per-file ITvRemoteServiceInput.aidl = file:/media/lib/tvremote/OWNERS
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index a0f6fb9..9147c12 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -2720,6 +2720,42 @@
*/
public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
+ /**
+ * The flag indicating whether this TV program is scrambled or not.
+ *
+ * <p>Use the same coding for scrambled in the underlying broadcast standard
+ * if {@code free_ca_mode} in EIT is defined there (e.g. ETSI EN 300 468).
+ *
+ * <p>Type: INTEGER (boolean)
+ */
+ public static final String COLUMN_SCRAMBLED = "scrambled";
+
+ /**
+ * The comma-separated series IDs of this TV program for episodic TV shows.
+ *
+ * <p>This is used to indicate the series IDs.
+ * Programs in the same series share a series ID.
+ * Use this instead of {@link #COLUMN_SERIES_ID} if more than one series IDs
+ * are assigned to the TV program.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
+
+ /**
+ * The internal ID used by individual TV input services.
+ *
+ * <p>This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+
private Programs() {}
/** Canonical genres for TV programs. */
@@ -3052,6 +3088,32 @@
public static final String COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS =
"recording_expire_time_utc_millis";
+ /**
+ * The comma-separated series IDs of this TV program for episodic TV shows.
+ *
+ * <p>This is used to indicate the series IDs.
+ * Programs in the same series share a series ID.
+ * Use this instead of {@link #COLUMN_SERIES_ID} if more than one series IDs
+ * are assigned to the TV program.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
+
+ /**
+ * The internal ID used by individual TV input services.
+ *
+ * <p>This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ *
+ * <p>Can be empty.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+
private RecordedPrograms() {}
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 34e4609..efd9d10 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1390,6 +1390,57 @@
}
/**
+ * Returns available extension interfaces of a given hardware TV input. This can be used to
+ * provide domain-specific features that are only known between certain hardware TV inputs
+ * and their clients.
+ *
+ * @param inputId The ID of the TV input.
+ * @return a non-null list of extension interface names available to the caller. An empty
+ * list indicates the given TV input is not found, or the given TV input is not a
+ * hardware TV input, or the given TV input doesn't support any extension
+ * interfaces, or the caller doesn't hold the required permission for the extension
+ * interfaces supported by the given TV input.
+ * @see #getExtensionInterface
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public List<String> getAvailableExtensionInterfaceNames(@NonNull String inputId) {
+ Preconditions.checkNotNull(inputId);
+ try {
+ return mService.getAvailableExtensionInterfaceNames(inputId, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns an extension interface of a given hardware TV input. This can be used to provide
+ * domain-specific features that are only known between certain hardware TV inputs and
+ * their clients.
+ *
+ * @param inputId The ID of the TV input.
+ * @param name The extension interface name.
+ * @return an {@link IBinder} for the given extension interface, {@code null} if the given TV
+ * input is not found, or if the given TV input is not a hardware TV input, or if the
+ * given TV input doesn't support the given extension interface, or if the caller
+ * doesn't hold the required permission for the given extension interface.
+ * @see #getAvailableExtensionInterfaceNames
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public IBinder getExtensionInterface(@NonNull String inputId, @NonNull String name) {
+ Preconditions.checkNotNull(inputId);
+ Preconditions.checkNotNull(name);
+ try {
+ return mService.getExtensionInterface(inputId, name, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Registers a {@link TvInputCallback}.
*
* @param callback A callback used to monitor status of the TV inputs.
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 77fb2b2..4a429fb 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -201,6 +201,21 @@
}
@Override
+ public List<String> getAvailableExtensionInterfaceNames() {
+ return TvInputService.this.getAvailableExtensionInterfaceNames();
+ }
+
+ @Override
+ public IBinder getExtensionInterface(String name) {
+ return TvInputService.this.getExtensionInterface(name);
+ }
+
+ @Override
+ public String getExtensionInterfacePermission(String name) {
+ return TvInputService.this.getExtensionInterfacePermission(name);
+ }
+
+ @Override
public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_INPUT,
hardwareInfo).sendToTarget();
@@ -253,6 +268,67 @@
}
/**
+ * Returns available extension interfaces. This can be used to provide domain-specific
+ * features that are only known between certain hardware TV inputs and their clients.
+ *
+ * <p>Note that this service-level extension interface mechanism is only for hardware
+ * TV inputs that are bound even when sessions are not created.
+ *
+ * @return a non-null list of available extension interface names. An empty list
+ * indicates the TV input doesn't support any extension interfaces.
+ * @see #getExtensionInterface
+ * @see #getExtensionInterfacePermission
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public List<String> getAvailableExtensionInterfaceNames() {
+ return new ArrayList<>();
+ }
+
+ /**
+ * Returns an extension interface. This can be used to provide domain-specific features
+ * that are only known between certain hardware TV inputs and their clients.
+ *
+ * <p>Note that this service-level extension interface mechanism is only for hardware
+ * TV inputs that are bound even when sessions are not created.
+ *
+ * @param name The extension interface name.
+ * @return an {@link IBinder} for the given extension interface, {@code null} if the TV input
+ * doesn't support the given extension interface.
+ * @see #getAvailableExtensionInterfaceNames
+ * @see #getExtensionInterfacePermission
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public IBinder getExtensionInterface(@NonNull String name) {
+ return null;
+ }
+
+ /**
+ * Returns a permission for the given extension interface. This can be used to provide
+ * domain-specific features that are only known between certain hardware TV inputs and their
+ * clients.
+ *
+ * <p>Note that this service-level extension interface mechanism is only for hardware
+ * TV inputs that are bound even when sessions are not created.
+ *
+ * @param name The extension interface name.
+ * @return a name of the permission being checked for the given extension interface,
+ * {@code null} if there is no required permission, or if the TV input doesn't
+ * support the given extension interface.
+ * @see #getAvailableExtensionInterfaceNames
+ * @see #getExtensionInterface
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public String getExtensionInterfacePermission(@NonNull String name) {
+ return null;
+ }
+
+ /**
* Returns a concrete implementation of {@link Session}.
*
* <p>May return {@code null} if this TV input service fails to create a session for some
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index bbbef38..773cdc9 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -228,10 +228,9 @@
return mStreamManager->getPairStream(this);
}
-Stream* Stream::playPairStream() {
+Stream* Stream::playPairStream(std::vector<std::any>& garbage) {
Stream* pairStream = getPairStream();
LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
- sp<AudioTrack> releaseTracks[2];
{
ALOGV("%s: track streamID: %d", __func__, (int)getStreamID());
// TODO: Do we really want to force a simultaneous synchronization between
@@ -260,7 +259,7 @@
const int pairState = pairStream->mState;
pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
- pairStream->mLoop, pairStream->mRate, releaseTracks);
+ pairStream->mLoop, pairStream->mRate, garbage);
if (pairStream->mState == IDLE) {
return nullptr; // AudioTrack error
}
@@ -269,17 +268,16 @@
pairStream->mAudioTrack->pause();
}
}
- // release tracks outside of Stream lock
return pairStream;
}
void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
- sp<AudioTrack> releaseTracks[2])
+ std::vector<std::any>& garbage)
{
- // These tracks are released without the lock.
- sp<AudioTrack> &oldTrack = releaseTracks[0];
- sp<AudioTrack> &newTrack = releaseTracks[1];
+ // oldTrack and newTrack are placeholders to be released by garbage without the lock.
+ sp<AudioTrack> oldTrack;
+ sp<AudioTrack> newTrack;
status_t status = NO_ERROR;
{
@@ -383,8 +381,12 @@
mState = IDLE;
mSoundID = 0;
mSound.reset();
- mAudioTrack.clear(); // actual release from releaseTracks[]
+ mAudioTrack.clear(); // actual release from garbage
}
+
+ // move tracks to garbage to be released later outside of lock.
+ if (newTrack) garbage.emplace_back(std::move(newTrack));
+ if (oldTrack) garbage.emplace_back(std::move(oldTrack));
}
/* static */
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
index d4e5c9f..aa0eef5 100644
--- a/media/jni/soundpool/Stream.h
+++ b/media/jni/soundpool/Stream.h
@@ -18,6 +18,7 @@
#include "Sound.h"
+#include <any>
#include <android-base/thread_annotations.h>
#include <audio_utils/clock.h>
#include <media/AudioTrack.h>
@@ -90,8 +91,9 @@
void mute(bool muting);
void dump() const NO_THREAD_SAFETY_ANALYSIS; // disable for ALOGV (see func for details).
- // returns the pair stream if successful, nullptr otherwise
- Stream* playPairStream();
+ // returns the pair stream if successful, nullptr otherwise.
+ // garbage is used to release tracks and data outside of any lock.
+ Stream* playPairStream(std::vector<std::any>& garbage);
// These parameters are explicitly checked in the SoundPool class
// so never deviate from the Java API specified values.
@@ -123,9 +125,10 @@
Stream* getPairStream() const;
private:
+ // garbage is used to release tracks and data outside of any lock.
void play_l(const std::shared_ptr<Sound>& sound, int streamID,
float leftVolume, float rightVolume, int priority, int loop, float rate,
- sp<AudioTrack> releaseTracks[2]) REQUIRES(mLock);
+ std::vector<std::any>& garbage) REQUIRES(mLock);
void stop_l() REQUIRES(mLock);
void setVolume_l(float leftVolume, float rightVolume) REQUIRES(mLock);
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index 309d71c..7f987e3 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -157,6 +157,7 @@
__func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate);
bool launchThread = false;
int32_t streamID = 0;
+ std::vector<std::any> garbage;
{ // for lock
std::unique_lock lock(mStreamManagerLock);
@@ -243,7 +244,7 @@
removeFromQueues_l(newStream);
mProcessingStreams.emplace(newStream);
lock.unlock();
- if (Stream* nextStream = newStream->playPairStream()) {
+ if (Stream* nextStream = newStream->playPairStream(garbage)) {
lock.lock();
ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID());
addToActiveQueue_l(nextStream);
@@ -266,6 +267,7 @@
ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
}
ALOGV("%s: returning %d", __func__, streamID);
+ // garbage is cleared here outside mStreamManagerLock.
return streamID;
}
@@ -359,6 +361,7 @@
{
ALOGV("%s(%d) entering", __func__, id);
int64_t waitTimeNs = 0; // on thread start, mRestartStreams can be non-empty.
+ std::vector<std::any> garbage; // used for garbage collection
std::unique_lock lock(mStreamManagerLock);
while (!mQuit) {
if (waitTimeNs > 0) {
@@ -388,7 +391,7 @@
if (!mLockStreamManagerStop) lock.unlock();
stream->stop();
ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID());
- if (Stream* nextStream = stream->playPairStream()) {
+ if (Stream* nextStream = stream->playPairStream(garbage)) {
ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID());
if (!mLockStreamManagerStop) lock.lock();
if (nextStream->getStopTimeNs() > 0) {
@@ -405,6 +408,12 @@
}
mProcessingStreams.erase(stream);
sanityCheckQueue_l();
+ if (!garbage.empty()) {
+ lock.unlock();
+ // garbage audio tracks (etc) are cleared here outside mStreamManagerLock.
+ garbage.clear();
+ lock.lock();
+ }
}
}
ALOGV("%s(%d) exiting", __func__, id);
diff --git a/native/android/OWNERS b/native/android/OWNERS
index 02dfd39..8b35f8d 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -1,7 +1,7 @@
jreck@google.com
per-file libandroid_net.map.txt, net.c = set noparent
-per-file libandroid_net.map.txt, net.c = codewiz@google.com, jchalard@google.com, junyulai@google.com
+per-file libandroid_net.map.txt, net.c = jchalard@google.com, junyulai@google.com
per-file libandroid_net.map.txt, net.c = lorenzo@google.com, reminv@google.com, satk@google.com
per-file system_fonts.cpp = file:/graphics/java/android/graphics/fonts/OWNERS
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index 2b81200..d80317b 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -28,8 +28,5 @@
rust: {
enabled: true,
},
- ndk: {
- separate_platform_variant: false,
- },
},
}
diff --git a/packages/ConnectivityT/OWNERS b/packages/ConnectivityT/OWNERS
index 4862377..e267d19 100644
--- a/packages/ConnectivityT/OWNERS
+++ b/packages/ConnectivityT/OWNERS
@@ -1 +1,2 @@
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
\ No newline at end of file
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 3e82b28..0bda923 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -100,6 +100,37 @@
],
}
+// IpSec related libraries.
+
+filegroup {
+ name: "framework-connectivity-ipsec-sources",
+ srcs: [
+ "src/android/net/IIpSecService.aidl",
+ "src/android/net/IpSec*.*",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+// Ethernet related libraries.
+
+filegroup {
+ name: "framework-connectivity-ethernet-sources",
+ srcs: [
+ "src/android/net/EthernetManager.java",
+ "src/android/net/EthernetNetworkSpecifier.java",
+ "src/android/net/IEthernetManager.aidl",
+ "src/android/net/IEthernetServiceListener.aidl",
+ "src/android/net/ITetheredInterfaceCallback.aidl",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
// Connectivity-T common libraries.
filegroup {
@@ -116,11 +147,11 @@
filegroup {
name: "framework-connectivity-tiramisu-sources",
srcs: [
+ ":framework-connectivity-ethernet-sources",
+ ":framework-connectivity-ipsec-sources",
":framework-connectivity-netstats-sources",
":framework-connectivity-nsd-sources",
":framework-connectivity-tiramisu-internal-sources",
],
- visibility: [
- "//frameworks/base",
- ],
+ visibility: ["//frameworks/base"],
}
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
index 216a4a0..f684a4d 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
@@ -24,13 +24,15 @@
import android.net.NetworkTemplate;
import android.net.TrafficStats;
import android.os.RemoteException;
-import android.util.IntArray;
import android.util.Log;
+import com.android.net.module.util.CollectionUtils;
+
import dalvik.system.CloseGuard;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
/**
* Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects
@@ -568,7 +570,7 @@
// the filtering logic below can be removed.
int[] uids = mSession.getRelevantUids();
// Filtering of uids with empty history.
- IntArray filteredUids = new IntArray(uids.length);
+ final ArrayList<Integer> filteredUids = new ArrayList<>();
for (int uid : uids) {
try {
NetworkStatsHistory history = mSession.getHistoryIntervalForUid(mTemplate, uid,
@@ -581,7 +583,7 @@
Log.w(TAG, "Error while getting history of uid " + uid, e);
}
}
- mUids = filteredUids.toArray();
+ mUids = CollectionUtils.toIntArray(filteredUids);
mUidOrUidIndex = -1;
stepHistory();
}
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 8a6c85d..a316b8a 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -45,11 +45,8 @@
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.DataUnit;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -121,7 +118,7 @@
* is reached.
* @hide
*/
- public static final long MIN_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(2);
+ public static final long MIN_THRESHOLD_BYTES = 2 * 1_048_576L; // 2MiB
private final Context mContext;
private final INetworkStatsService mService;
@@ -135,21 +132,13 @@
private int mFlags;
- /**
- * {@hide}
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public NetworkStatsManager(Context context) throws ServiceNotFoundException {
- this(context, INetworkStatsService.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE)));
- }
-
/** @hide */
@VisibleForTesting
public NetworkStatsManager(Context context, INetworkStatsService service) {
mContext = context;
mService = service;
setPollOnOpen(true);
+ setAugmentWithSubscriptionPlan(true);
}
/** @hide */
@@ -181,16 +170,44 @@
}
}
- /** @hide */
- public Bucket querySummaryForDevice(NetworkTemplate template,
- long startTime, long endTime) throws SecurityException, RemoteException {
- Bucket bucket = null;
- NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime,
- mService);
- bucket = stats.getDeviceSummaryForNetwork();
-
- stats.close();
- return bucket;
+ /**
+ * Query network usage statistics summaries.
+ *
+ * Result is summarised data usage for the whole
+ * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and
+ * roaming. This means the bucket's start and end timestamp will be the same as the
+ * 'startTime' and 'endTime' arguments. State is going to be
+ * {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL},
+ * tag {@link NetworkStats.Bucket#TAG_NONE},
+ * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered {@link NetworkStats.Bucket#METERED_ALL},
+ * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param startTime Start of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Bucket Summarised data usage.
+ *
+ * @hide
+ */
+ @NonNull
+ @WorkerThread
+ // @SystemApi(client = MODULE_LIBRARIES)
+ public Bucket querySummaryForDevice(@NonNull NetworkTemplate template,
+ long startTime, long endTime) {
+ try {
+ NetworkStats stats =
+ new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ Bucket bucket = stats.getDeviceSummaryForNetwork();
+ stats.close();
+ return bucket;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return null; // To make the compiler happy.
}
/**
@@ -334,14 +351,37 @@
return querySummary(template, startTime, endTime);
}
- /** @hide */
- public NetworkStats querySummary(NetworkTemplate template, long startTime,
- long endTime) throws SecurityException, RemoteException {
- NetworkStats result;
- result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
- result.startSummaryEnumeration();
-
- return result;
+ /**
+ * Query network usage statistics summaries.
+ *
+ * The results will only include traffic made by UIDs belonging to the calling user profile.
+ * The results are aggregated over time, so that all buckets will have the same start and
+ * end timestamps as the passed arguments. Not aggregated over state, uid, default network,
+ * metered, or roaming.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param startTime Start of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Statistics which is described above.
+ * @hide
+ */
+ @Nullable
+ // @SystemApi(client = MODULE_LIBRARIES)
+ @WorkerThread
+ public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime,
+ long endTime) throws SecurityException {
+ try {
+ NetworkStats result =
+ new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ result.startSummaryEnumeration();
+ return result;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return null; // To make the compiler happy.
}
/**
diff --git a/core/java/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
similarity index 76%
rename from core/java/android/net/EthernetManager.java
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index 7cd63ef..ece54df 100644
--- a/core/java/android/net/EthernetManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -16,7 +16,9 @@
package android.net;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -32,6 +34,7 @@
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
/**
* A class representing the IP configuration of the Ethernet network.
@@ -315,4 +318,83 @@
}
return new TetheredInterfaceRequest(mService, cbInternal);
}
+
+ private static final class InternalNetworkManagementListener
+ extends IInternalNetworkManagementListener.Stub {
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final BiConsumer<Network, InternalNetworkManagementException> mListener;
+
+ InternalNetworkManagementListener(
+ @NonNull final Executor executor,
+ @NonNull final BiConsumer<Network, InternalNetworkManagementException> listener) {
+ Objects.requireNonNull(executor, "Pass a non-null executor");
+ Objects.requireNonNull(listener, "Pass a non-null listener");
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onComplete(
+ @Nullable final Network network,
+ @Nullable final InternalNetworkManagementException e) {
+ mExecutor.execute(() -> mListener.accept(network, e));
+ }
+ }
+
+ private InternalNetworkManagementListener getInternalNetworkManagementListener(
+ @Nullable final Executor executor,
+ @Nullable final BiConsumer<Network, InternalNetworkManagementException> listener) {
+ if (null != listener) {
+ Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener");
+ }
+ final InternalNetworkManagementListener proxy;
+ if (null == listener) {
+ proxy = null;
+ } else {
+ proxy = new InternalNetworkManagementListener(executor, listener);
+ }
+ return proxy;
+ }
+
+ private void updateConfiguration(
+ @NonNull String iface,
+ @NonNull InternalNetworkUpdateRequest request,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+ final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
+ executor, listener);
+ try {
+ mService.updateConfiguration(iface, request, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void connectNetwork(
+ @NonNull String iface,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+ final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
+ executor, listener);
+ try {
+ mService.connectNetwork(iface, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void disconnectNetwork(
+ @NonNull String iface,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+ final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
+ executor, listener);
+ try {
+ mService.disconnectNetwork(iface, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/net/EthernetNetworkSpecifier.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
similarity index 100%
rename from core/java/android/net/EthernetNetworkSpecifier.java
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
diff --git a/core/java/android/net/IEthernetManager.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
similarity index 77%
rename from core/java/android/net/IEthernetManager.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
index e058e5a..e688bea 100644
--- a/core/java/android/net/IEthernetManager.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
@@ -18,6 +18,8 @@
import android.net.IpConfiguration;
import android.net.IEthernetServiceListener;
+import android.net.IInternalNetworkManagementListener;
+import android.net.InternalNetworkUpdateRequest;
import android.net.ITetheredInterfaceCallback;
/**
@@ -36,4 +38,8 @@
void setIncludeTestInterfaces(boolean include);
void requestTetheredInterface(in ITetheredInterfaceCallback callback);
void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
+ void updateConfiguration(String iface, in InternalNetworkUpdateRequest request,
+ in IInternalNetworkManagementListener listener);
+ void connectNetwork(String iface, in IInternalNetworkManagementListener listener);
+ void disconnectNetwork(String iface, in IInternalNetworkManagementListener listener);
}
diff --git a/core/java/android/net/IEthernetServiceListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
similarity index 100%
rename from core/java/android/net/IEthernetServiceListener.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
diff --git a/core/java/android/net/IIpSecService.aidl b/packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
similarity index 100%
rename from core/java/android/net/IIpSecService.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
diff --git a/core/java/android/net/ITetheredInterfaceCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/ITetheredInterfaceCallback.aidl
similarity index 100%
rename from core/java/android/net/ITetheredInterfaceCallback.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/ITetheredInterfaceCallback.aidl
diff --git a/core/java/android/net/IpSecAlgorithm.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
similarity index 97%
rename from core/java/android/net/IpSecAlgorithm.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
index 8605248..a84e7a9 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
@@ -23,7 +23,6 @@
import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -297,7 +296,7 @@
return mTruncLenBits;
}
- /* Parcelable Implementation */
+ /** Parcelable Implementation */
public int describeContents() {
return 0;
}
@@ -469,20 +468,12 @@
}
}
- // Because encryption keys are sensitive and userdebug builds are used by large user pools
- // such as beta testers, we only allow sensitive info such as keys on eng builds.
- private static boolean isUnsafeBuild() {
- return Build.IS_DEBUGGABLE && Build.IS_ENG;
- }
-
@Override
@NonNull
public String toString() {
return new StringBuilder()
.append("{mName=")
.append(mName)
- .append(", mKey=")
- .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
.append(", mTruncLenBits=")
.append(mTruncLenBits)
.append("}")
diff --git a/core/java/android/net/IpSecConfig.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
similarity index 100%
rename from core/java/android/net/IpSecConfig.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
diff --git a/core/java/android/net/IpSecConfig.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
similarity index 100%
rename from core/java/android/net/IpSecConfig.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
diff --git a/core/java/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
similarity index 98%
rename from core/java/android/net/IpSecManager.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
index c106807..0d15dff 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
@@ -17,8 +17,6 @@
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.annotation.NonNull;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
@@ -46,6 +44,7 @@
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
+import java.util.Objects;
/**
* This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
@@ -86,6 +85,7 @@
*
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int DIRECTION_FWD = 2;
/**
@@ -99,9 +99,9 @@
/** @hide */
public interface Status {
- public static final int OK = 0;
- public static final int RESOURCE_UNAVAILABLE = 1;
- public static final int SPI_UNAVAILABLE = 2;
+ int OK = 0;
+ int RESOURCE_UNAVAILABLE = 1;
+ int SPI_UNAVAILABLE = 2;
}
/** @hide */
@@ -276,7 +276,7 @@
* @param destinationAddress the destination address for traffic bearing the requested SPI.
* For inbound traffic, the destination should be an address currently assigned on-device.
* @return the reserved SecurityParameterIndex
- * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+ * @throws ResourceUnavailableException indicating that too many SPIs are
* currently allocated for this user
*/
@NonNull
@@ -307,9 +307,9 @@
* @param requestedSpi the requested SPI. The range 1-255 is reserved and may not be used. See
* RFC 4303 Section 2.1.
* @return the reserved SecurityParameterIndex
- * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+ * @throws ResourceUnavailableException indicating that too many SPIs are
* currently allocated for this user
- * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be
+ * @throws SpiUnavailableException indicating that the requested SPI could not be
* reserved
*/
@NonNull
@@ -988,7 +988,7 @@
*/
public IpSecManager(Context ctx, IIpSecService service) {
mContext = ctx;
- mService = checkNotNull(service, "missing service");
+ mService = Objects.requireNonNull(service, "missing service");
}
private static void maybeHandleServiceSpecificException(ServiceSpecificException sse) {
diff --git a/core/java/android/net/IpSecSpiResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecSpiResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
diff --git a/core/java/android/net/IpSecSpiResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
similarity index 100%
rename from core/java/android/net/IpSecSpiResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
diff --git a/core/java/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
similarity index 96%
rename from core/java/android/net/IpSecTransform.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
index b48c1fd..36199a0 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
@@ -33,7 +33,6 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
@@ -41,6 +40,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
+import java.util.Objects;
/**
* This class represents a transform, which roughly corresponds to an IPsec Security Association.
@@ -255,7 +255,7 @@
@NonNull
public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
// TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
- Preconditions.checkNotNull(algo);
+ Objects.requireNonNull(algo);
mConfig.setEncryption(algo);
return this;
}
@@ -270,7 +270,7 @@
@NonNull
public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
// TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
- Preconditions.checkNotNull(algo);
+ Objects.requireNonNull(algo);
mConfig.setAuthentication(algo);
return this;
}
@@ -290,7 +290,7 @@
*/
@NonNull
public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
- Preconditions.checkNotNull(algo);
+ Objects.requireNonNull(algo);
mConfig.setAuthenticatedEncryption(algo);
return this;
}
@@ -311,7 +311,7 @@
@NonNull
public IpSecTransform.Builder setIpv4Encapsulation(
@NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
- Preconditions.checkNotNull(localSocket);
+ Objects.requireNonNull(localSocket);
mConfig.setEncapType(ENCAP_ESPINUDP);
if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
@@ -348,8 +348,8 @@
@NonNull IpSecManager.SecurityParameterIndex spi)
throws IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException, IOException {
- Preconditions.checkNotNull(sourceAddress);
- Preconditions.checkNotNull(spi);
+ Objects.requireNonNull(sourceAddress);
+ Objects.requireNonNull(spi);
if (spi.getResourceId() == INVALID_RESOURCE_ID) {
throw new IllegalArgumentException("Invalid SecurityParameterIndex");
}
@@ -387,8 +387,8 @@
@NonNull IpSecManager.SecurityParameterIndex spi)
throws IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException, IOException {
- Preconditions.checkNotNull(sourceAddress);
- Preconditions.checkNotNull(spi);
+ Objects.requireNonNull(sourceAddress);
+ Objects.requireNonNull(spi);
if (spi.getResourceId() == INVALID_RESOURCE_ID) {
throw new IllegalArgumentException("Invalid SecurityParameterIndex");
}
@@ -404,7 +404,7 @@
* @param context current context
*/
public Builder(@NonNull Context context) {
- Preconditions.checkNotNull(context);
+ Objects.requireNonNull(context);
mContext = context;
mConfig = new IpSecConfig();
}
diff --git a/core/java/android/net/IpSecTransformResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecTransformResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
diff --git a/core/java/android/net/IpSecTransformResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
similarity index 94%
rename from core/java/android/net/IpSecTransformResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
index a384889..363f316 100644
--- a/core/java/android/net/IpSecTransformResponse.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
@@ -60,7 +60,8 @@
resourceId = in.readInt();
}
- public static final @android.annotation.NonNull Parcelable.Creator<IpSecTransformResponse> CREATOR =
+ @android.annotation.NonNull
+ public static final Parcelable.Creator<IpSecTransformResponse> CREATOR =
new Parcelable.Creator<IpSecTransformResponse>() {
public IpSecTransformResponse createFromParcel(Parcel in) {
return new IpSecTransformResponse(in);
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecTunnelInterfaceResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
similarity index 95%
rename from core/java/android/net/IpSecTunnelInterfaceResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
index e3411e0..127e30a 100644
--- a/core/java/android/net/IpSecTunnelInterfaceResponse.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
@@ -65,7 +65,8 @@
interfaceName = in.readString();
}
- public static final @android.annotation.NonNull Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
+ @android.annotation.NonNull
+ public static final Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
new Parcelable.Creator<IpSecTunnelInterfaceResponse>() {
public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) {
return new IpSecTunnelInterfaceResponse(in);
diff --git a/core/java/android/net/IpSecUdpEncapResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecUdpEncapResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
diff --git a/core/java/android/net/IpSecUdpEncapResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
similarity index 96%
rename from core/java/android/net/IpSecUdpEncapResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
index 4e7ba9b..732cf19 100644
--- a/core/java/android/net/IpSecUdpEncapResponse.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
@@ -18,6 +18,7 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+
import java.io.FileDescriptor;
import java.io.IOException;
@@ -83,7 +84,8 @@
fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
}
- public static final @android.annotation.NonNull Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
+ @android.annotation.NonNull
+ public static final Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
new Parcelable.Creator<IpSecUdpEncapResponse>() {
public IpSecUdpEncapResponse createFromParcel(Parcel in) {
return new IpSecUdpEncapResponse(in);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index eb8f43e..8f1115e 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -21,7 +21,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
import android.service.NetworkIdentityProto;
import android.telephony.Annotation.NetworkType;
import android.util.proto.ProtoOutputStream;
@@ -228,11 +227,11 @@
final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities());
if (legacyType == TYPE_WIFI) {
- networkId = snapshot.getNetworkCapabilities().getSsid();
- if (networkId == null) {
- final WifiManager wifi = context.getSystemService(WifiManager.class);
- final WifiInfo info = wifi.getConnectionInfo();
- networkId = info != null ? info.getSSID() : null;
+ final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
+ .getTransportInfo();
+ if (transportInfo instanceof WifiInfo) {
+ final WifiInfo info = (WifiInfo) transportInfo;
+ networkId = info != null ? info.getCurrentNetworkKey() : null;
}
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
index c7ffc19..181a594 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
@@ -16,7 +16,7 @@
package android.net;
-import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational;
+import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -31,7 +31,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
+import com.android.net.module.util.CollectionUtils;
import libcore.util.EmptyArray;
@@ -1185,7 +1185,7 @@
* @hide
*/
public void removeUids(int[] uids) {
- filter(e -> !ArrayUtils.contains(uids, e.uid));
+ filter(e -> !CollectionUtils.contains(uids, e.uid));
}
/**
@@ -1218,7 +1218,7 @@
filter(e -> (limitUid == UID_ALL || limitUid == e.uid)
&& (limitTag == TAG_ALL || limitTag == e.tag)
&& (limitIfaces == INTERFACES_ALL
- || ArrayUtils.contains(limitIfaces, e.iface)));
+ || CollectionUtils.contains(limitIfaces, e.iface)));
}
/**
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
index 3885a9e..b64fbdb 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
@@ -17,6 +17,7 @@
package android.net;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;
@@ -24,7 +25,7 @@
import android.Manifest;
import android.annotation.IntDef;
import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -32,8 +33,6 @@
import android.os.UserHandle;
import android.telephony.TelephonyManager;
-import com.android.server.LocalServices;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -108,9 +107,8 @@
/** Returns the {@link NetworkStatsAccess.Level} for the given caller. */
public static @NetworkStatsAccess.Level int checkAccessLevel(
- Context context, int callingUid, String callingPackage) {
- final DevicePolicyManagerInternal dpmi = LocalServices.getService(
- DevicePolicyManagerInternal.class);
+ Context context, int callingPid, int callingUid, String callingPackage) {
+ final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class);
final TelephonyManager tm = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE);
boolean hasCarrierPrivileges;
@@ -123,10 +121,15 @@
Binder.restoreCallingIdentity(token);
}
- final boolean isDeviceOwner = dpmi != null && dpmi.isActiveDeviceOwner(callingUid);
+ final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
final int appId = UserHandle.getAppId(callingUid);
+
+ final boolean isNetworkStack = context.checkPermission(
+ android.Manifest.permission.NETWORK_STACK, callingPid, callingUid)
+ == PERMISSION_GRANTED;
+
if (hasCarrierPrivileges || isDeviceOwner
- || appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) {
+ || appId == Process.SYSTEM_UID || isNetworkStack) {
// Carrier-privileged apps and device owners, and the system (including the
// network stack) can access data usage for all apps on the device.
return NetworkStatsAccess.Level.DEVICE;
@@ -139,8 +142,8 @@
}
//TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
- boolean isProfileOwner = dpmi != null && (dpmi.isActiveProfileOwner(callingUid)
- || dpmi.isActiveDeviceOwner(callingUid));
+ boolean isProfileOwner = mDpm != null && (mDpm.isProfileOwnerApp(callingPackage)
+ || mDpm.isDeviceOwnerApp(callingPackage));
if (isProfileOwner) {
// Apps with the AppOps permission, profile owners, and apps with the privileged
// permission can access data usage for all apps in this user/profile.
@@ -157,6 +160,8 @@
*/
public static boolean isAccessibleToUser(int uid, int callerUid,
@NetworkStatsAccess.Level int accessLevel) {
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ final int callerUserId = UserHandle.getUserHandleForUid(callerUid).getIdentifier();
switch (accessLevel) {
case NetworkStatsAccess.Level.DEVICE:
// Device-level access - can access usage for any uid.
@@ -167,13 +172,13 @@
// anonymized uids
return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
|| uid == UID_TETHERING || uid == UID_ALL
- || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
+ || userId == callerUserId;
case NetworkStatsAccess.Level.USER:
// User-level access - can access usage for any app running in the same user, along
// with some special uids (system, removed, or tethering).
return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
|| uid == UID_TETHERING
- || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
+ || userId == callerUserId;
case NetworkStatsAccess.Level.DEFAULT:
default:
// Default access level - can only access one's own usage.
@@ -187,8 +192,8 @@
AppOpsManager appOps = (AppOpsManager) context.getSystemService(
Context.APP_OPS_SERVICE);
- final int mode = appOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS,
- callingUid, callingPackage);
+ final int mode = appOps.noteOp(AppOpsManager.OPSTR_GET_USAGE_STATS,
+ callingUid, callingPackage, null /* attributionTag */, null /* message */);
if (mode == AppOpsManager.MODE_DEFAULT) {
// The default behavior here is to check if PackageManager has given the app
// permission.
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index 0d3b9ed..7935d28 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
@@ -30,7 +30,7 @@
import static android.net.TrafficStats.UID_REMOVED;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
-import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational;
+import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
import android.os.Binder;
import android.service.NetworkStatsCollectionKeyProto;
@@ -40,21 +40,17 @@
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
-import android.util.IntArray;
-import android.util.MathUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
import android.util.Range;
-import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastDataInput;
import com.android.internal.util.FastDataOutput;
import com.android.internal.util.FileRotator;
-import com.android.internal.util.IndentingPrintWriter;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetworkStatsUtils;
import libcore.io.IoUtils;
@@ -196,11 +192,11 @@
public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
final int callerUid) {
- IntArray uids = new IntArray();
+ final ArrayList<Integer> uids = new ArrayList<>();
for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i);
if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
- int j = uids.binarySearch(key.uid);
+ int j = Collections.binarySearch(uids, new Integer(key.uid));
if (j < 0) {
j = ~j;
@@ -208,7 +204,7 @@
}
}
}
- return uids.toArray();
+ return CollectionUtils.toIntArray(uids);
}
/**
@@ -225,7 +221,8 @@
// 180 days of history should be enough for anyone; if we end up needing
// more, we'll dynamically grow the history object.
- final int bucketEstimate = (int) MathUtils.constrain(((end - start) / mBucketDuration), 0,
+ final int bucketEstimate = (int) NetworkStatsUtils.constrain(
+ ((end - start) / mBucketDuration), 0,
(180 * DateUtils.DAY_IN_MILLIS) / mBucketDuration);
final NetworkStatsHistory combined = new NetworkStatsHistory(
mBucketDuration, bucketEstimate, fields);
@@ -316,7 +313,7 @@
final long deltaTotal = combined.getTotalBytes() - beforeTotal;
if (deltaTotal != 0) {
- Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes");
+ Log.d(TAG, "Augmented network usage by " + deltaTotal + " bytes");
}
// Finally we can slice data as originally requested
@@ -489,11 +486,11 @@
private void write(DataOutput out) throws IOException {
// cluster key lists grouped by ident
- final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
+ final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = new HashMap<>();
for (Key key : mStats.keySet()) {
ArrayList<Key> keys = keysByIdent.get(key.ident);
if (keys == null) {
- keys = Lists.newArrayList();
+ keys = new ArrayList<>();
keysByIdent.put(key.ident, keys);
}
keys.add(key);
@@ -640,12 +637,12 @@
* {@link TrafficStats#UID_REMOVED}.
*/
public void removeUids(int[] uids) {
- final ArrayList<Key> knownKeys = Lists.newArrayList();
+ final ArrayList<Key> knownKeys = new ArrayList<>();
knownKeys.addAll(mStats.keySet());
// migrate all UID stats into special "removed" bucket
for (Key key : knownKeys) {
- if (ArrayUtils.contains(uids, key.uid)) {
+ if (CollectionUtils.contains(uids, key.uid)) {
// only migrate combined TAG_NONE history
if (key.tag == TAG_NONE) {
final NetworkStatsHistory uidHistory = mStats.get(key);
@@ -672,7 +669,7 @@
}
private ArrayList<Key> getSortedKeys() {
- final ArrayList<Key> keys = Lists.newArrayList();
+ final ArrayList<Key> keys = new ArrayList<>();
keys.addAll(mStats.keySet());
Collections.sort(keys);
return keys;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
index a875e1a..428bc6d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
@@ -28,8 +28,7 @@
import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
-import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational;
-import static com.android.internal.util.ArrayUtils.total;
+import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -37,10 +36,11 @@
import android.os.Parcelable;
import android.service.NetworkStatsHistoryBucketProto;
import android.service.NetworkStatsHistoryProto;
-import android.util.MathUtils;
+import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetworkStatsUtils;
import libcore.util.EmptyArray;
@@ -174,7 +174,7 @@
txPackets = new long[bucketStart.length];
operations = new long[bucketStart.length];
bucketCount = bucketStart.length;
- totalBytes = total(rxBytes) + total(txBytes);
+ totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes);
break;
}
case VERSION_ADD_PACKETS:
@@ -189,7 +189,7 @@
txPackets = readVarLongArray(in);
operations = readVarLongArray(in);
bucketCount = bucketStart.length;
- totalBytes = total(rxBytes) + total(txBytes);
+ totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes);
break;
}
default: {
@@ -267,7 +267,7 @@
} else {
index -= 1;
}
- return MathUtils.constrain(index, 0, bucketCount - 1);
+ return NetworkStatsUtils.constrain(index, 0, bucketCount - 1);
}
/**
@@ -281,7 +281,7 @@
} else {
index += 1;
}
- return MathUtils.constrain(index, 0, bucketCount - 1);
+ return NetworkStatsUtils.constrain(index, 0, bucketCount - 1);
}
/**
@@ -349,6 +349,9 @@
// create any buckets needed by this range
ensureBuckets(start, end);
+ // Return fast if there is still no entry. This would typically happen when the start,
+ // end or duration are not valid values, e.g. start > end, negative duration value, etc.
+ if (bucketCount == 0) return;
// distribute data usage into buckets
long duration = end - start;
@@ -560,6 +563,9 @@
entry.txPackets = txPackets != null ? 0 : UNKNOWN;
entry.operations = operations != null ? 0 : UNKNOWN;
+ // Return fast if there is no entry.
+ if (bucketCount == 0) return entry;
+
final int startIndex = getIndexAfter(end);
for (int i = startIndex; i >= 0; i--) {
final long curStart = bucketStart[i];
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index 8b9c14d..659ad06 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -24,6 +24,8 @@
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkIdentity.OEM_NONE;
+import static android.net.NetworkIdentity.OEM_PAID;
+import static android.net.NetworkIdentity.OEM_PRIVATE;
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
@@ -33,8 +35,8 @@
import static android.net.NetworkStats.ROAMING_ALL;
import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.ROAMING_YES;
-import static android.net.wifi.WifiInfo.sanitizeSsid;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -44,15 +46,23 @@
import android.telephony.Annotation.NetworkType;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArraySet;
-import com.android.internal.util.ArrayUtils;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkIdentityUtils;
+import com.android.net.module.util.NetworkStatsUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
/**
* Predicate used to match {@link NetworkIdentity}, usually when collecting
@@ -60,40 +70,67 @@
*
* @hide
*/
-public class NetworkTemplate implements Parcelable {
- private static final String TAG = "NetworkTemplate";
+// @SystemApi(client = MODULE_LIBRARIES)
+public final class NetworkTemplate implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "MATCH_" }, value = {
+ MATCH_MOBILE,
+ MATCH_WIFI,
+ MATCH_ETHERNET,
+ MATCH_BLUETOOTH,
+ MATCH_CARRIER
+ })
+ public @interface TemplateMatchRule{}
+ /** Match rule to match cellular networks with given Subscriber Ids. */
public static final int MATCH_MOBILE = 1;
+ /** Match rule to match wifi networks. */
public static final int MATCH_WIFI = 4;
+ /** Match rule to match ethernet networks. */
public static final int MATCH_ETHERNET = 5;
+ /**
+ * Match rule to match all cellular networks.
+ *
+ * @hide
+ */
public static final int MATCH_MOBILE_WILDCARD = 6;
+ /**
+ * Match rule to match all wifi networks.
+ *
+ * @hide
+ */
public static final int MATCH_WIFI_WILDCARD = 7;
+ /** Match rule to match bluetooth networks. */
public static final int MATCH_BLUETOOTH = 8;
+ /**
+ * Match rule to match networks with {@link Connectivity#TYPE_PROXY} as the legacy network type.
+ *
+ * @hide
+ */
public static final int MATCH_PROXY = 9;
+ /**
+ * Match rule to match all networks with subscriberId inside the template. Some carriers
+ * may offer non-cellular networks like WiFi, which will be matched by this rule.
+ */
public static final int MATCH_CARRIER = 10;
- /**
- * Value of the match rule of the subscriberId to match networks with specific subscriberId.
- */
- public static final int SUBSCRIBER_ID_MATCH_RULE_EXACT = 0;
- /**
- * Value of the match rule of the subscriberId to match networks with any subscriberId which
- * includes null and non-null.
- */
- public static final int SUBSCRIBER_ID_MATCH_RULE_ALL = 1;
+ // TODO: Remove this and replace all callers with WIFI_NETWORK_KEY_ALL.
+ /** @hide */
+ public static final String WIFI_NETWORKID_ALL = null;
/**
- * Wi-Fi Network ID is never supposed to be null (if it is, it is a bug that
+ * Wi-Fi Network Key is never supposed to be null (if it is, it is a bug that
* should be fixed), so it's not possible to want to match null vs
- * non-null. Therefore it's fine to use null as a sentinel for Network ID.
+ * non-null. Therefore it's fine to use null as a sentinel for Wifi Network Key.
+ *
+ * @hide
*/
- public static final String WIFI_NETWORKID_ALL = null;
+ public static final String WIFI_NETWORK_KEY_ALL = WIFI_NETWORKID_ALL;
/**
* Include all network types when filtering. This is meant to merge in with the
* {@code TelephonyManager.NETWORK_TYPE_*} constants, and thus needs to stay in sync.
- *
- * @hide
*/
public static final int NETWORK_TYPE_ALL = -1;
/**
@@ -106,21 +143,37 @@
*/
public static final int NETWORK_TYPE_5G_NSA = -2;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "OEM_MANAGED_" }, value = {
+ OEM_MANAGED_ALL,
+ OEM_MANAGED_NO,
+ OEM_MANAGED_YES,
+ OEM_MANAGED_PAID,
+ OEM_MANAGED_PRIVATE
+ })
+ public @interface OemManaged{}
+
/**
* Value to match both OEM managed and unmanaged networks (all networks).
- * @hide
*/
public static final int OEM_MANAGED_ALL = -1;
/**
* Value to match networks which are not OEM managed.
- * @hide
*/
public static final int OEM_MANAGED_NO = OEM_NONE;
/**
* Value to match any OEM managed network.
- * @hide
*/
public static final int OEM_MANAGED_YES = -2;
+ /**
+ * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}.
+ */
+ public static final int OEM_MANAGED_PAID = OEM_PAID;
+ /**
+ * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}.
+ */
+ public static final int OEM_MANAGED_PRIVATE = OEM_PRIVATE;
private static boolean isKnownMatchRule(final int rule) {
switch (rule) {
@@ -142,6 +195,8 @@
/**
* Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
* the given IMSI.
+ *
+ * @hide
*/
@UnsupportedAppUsage
public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
@@ -152,22 +207,29 @@
* Template to match cellular networks with the given IMSI, {@code ratType} and
* {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when
* filtering. See {@code TelephonyManager.NETWORK_TYPE_*}.
+ *
+ * @hide
*/
public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
@NetworkType int ratType, int metered) {
if (TextUtils.isEmpty(subscriberId)) {
- return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null, null,
- metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
- SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null /* subscriberId */,
+ null /* matchSubscriberIds */,
+ new String[0] /* matchWifiNetworkKeys */, metered, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
- return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[]{subscriberId}, null,
+ return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[] { subscriberId },
+ new String[0] /* matchWifiNetworkKeys */,
metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
- SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
/**
* Template to match metered {@link ConnectivityManager#TYPE_MOBILE} networks,
* regardless of IMSI.
+ *
+ * @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static NetworkTemplate buildTemplateMobileWildcard() {
@@ -176,7 +238,9 @@
/**
* Template to match all metered {@link ConnectivityManager#TYPE_WIFI} networks,
- * regardless of SSID.
+ * regardless of key of the wifi network.
+ *
+ * @hide
*/
@UnsupportedAppUsage
public static NetworkTemplate buildTemplateWifiWildcard() {
@@ -185,6 +249,7 @@
return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
}
+ /** @hide */
@Deprecated
@UnsupportedAppUsage
public static NetworkTemplate buildTemplateWifi() {
@@ -193,34 +258,48 @@
/**
* Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
- * given SSID.
+ * given key of the wifi network.
+ *
+ * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+ * to know details about the key.
+ * @hide
*/
- public static NetworkTemplate buildTemplateWifi(@NonNull String networkId) {
- Objects.requireNonNull(networkId);
+ public static NetworkTemplate buildTemplateWifi(@NonNull String wifiNetworkKey) {
+ Objects.requireNonNull(wifiNetworkKey);
return new NetworkTemplate(MATCH_WIFI, null /* subscriberId */,
new String[] { null } /* matchSubscriberIds */,
- networkId, METERED_ALL, ROAMING_ALL,
+ new String[] { wifiNetworkKey }, METERED_ALL, ROAMING_ALL,
DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
- SUBSCRIBER_ID_MATCH_RULE_ALL);
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL);
}
/**
- * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given SSID,
- * and IMSI.
+ * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given
+ * key of the wifi network and IMSI.
*
- * Call with {@link #WIFI_NETWORKID_ALL} for {@code networkId} to get result regardless of SSID.
+ * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless
+ * of key of the wifi network.
+ *
+ * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+ * to know details about the key.
+ * @param subscriberId the IMSI associated to this wifi network.
+ *
+ * @hide
*/
- public static NetworkTemplate buildTemplateWifi(@Nullable String networkId,
+ public static NetworkTemplate buildTemplateWifi(@Nullable String wifiNetworkKey,
@Nullable String subscriberId) {
return new NetworkTemplate(MATCH_WIFI, subscriberId, new String[] { subscriberId },
- networkId, METERED_ALL, ROAMING_ALL,
- DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
- SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ wifiNetworkKey != null
+ ? new String[] { wifiNetworkKey } : new String[0],
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
/**
* Template to combine all {@link ConnectivityManager#TYPE_ETHERNET} style
* networks together.
+ *
+ * @hide
*/
@UnsupportedAppUsage
public static NetworkTemplate buildTemplateEthernet() {
@@ -230,6 +309,8 @@
/**
* Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
* networks together.
+ *
+ * @hide
*/
public static NetworkTemplate buildTemplateBluetooth() {
return new NetworkTemplate(MATCH_BLUETOOTH, null, null);
@@ -238,6 +319,8 @@
/**
* Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
* networks together.
+ *
+ * @hide
*/
public static NetworkTemplate buildTemplateProxy() {
return new NetworkTemplate(MATCH_PROXY, null, null);
@@ -245,13 +328,17 @@
/**
* Template to match all metered carrier networks with the given IMSI.
+ *
+ * @hide
*/
public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
Objects.requireNonNull(subscriberId);
return new NetworkTemplate(MATCH_CARRIER, subscriberId,
- new String[] { subscriberId }, null /* networkId */, METERED_YES, ROAMING_ALL,
+ new String[] { subscriberId },
+ new String[0] /* matchWifiNetworkKeys */,
+ METERED_YES, ROAMING_ALL,
DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
- SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
private final int mMatchRule;
@@ -267,7 +354,8 @@
*/
private final String[] mMatchSubscriberIds;
- private final String mNetworkId;
+ @NonNull
+ private final String[] mMatchWifiNetworkKeys;
// Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
private final int mMetered;
@@ -283,14 +371,14 @@
// Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}.
private final int mOemManaged;
- private void checkValidSubscriberIdMatchRule() {
- switch (mMatchRule) {
+ private static void checkValidSubscriberIdMatchRule(int matchRule, int subscriberIdMatchRule) {
+ switch (matchRule) {
case MATCH_MOBILE:
case MATCH_CARRIER:
// MOBILE and CARRIER templates must always specify a subscriber ID.
- if (mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) {
- throw new IllegalArgumentException("Invalid SubscriberIdMatchRule"
- + "on match rule: " + getMatchRuleName(mMatchRule));
+ if (subscriberIdMatchRule == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL) {
+ throw new IllegalArgumentException("Invalid SubscriberIdMatchRule "
+ + "on match rule: " + getMatchRuleName(matchRule));
}
return;
default:
@@ -298,48 +386,56 @@
}
}
+ /** @hide */
// TODO: Deprecate this constructor, mark it @UnsupportedAppUsage(maxTargetSdk = S)
@UnsupportedAppUsage
- public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
- this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
+ public NetworkTemplate(int matchRule, String subscriberId, String wifiNetworkKey) {
+ this(matchRule, subscriberId, new String[] { subscriberId }, wifiNetworkKey);
}
+ /** @hide */
public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
- String networkId) {
+ String wifiNetworkKey) {
// Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
// to metered networks. It is now possible to match mobile with any meteredness, but
// in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
//constructor passes METERED_YES for these types.
- this(matchRule, subscriberId, matchSubscriberIds, networkId,
+ this(matchRule, subscriberId, matchSubscriberIds,
+ wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
(matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD) ? METERED_YES
: METERED_ALL , ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
+ /** @hide */
// TODO: Remove it after updating all of the caller.
public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
- String networkId, int metered, int roaming, int defaultNetwork, int subType,
+ String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int subType,
int oemManaged) {
- this(matchRule, subscriberId, matchSubscriberIds, networkId, metered, roaming,
- defaultNetwork, subType, oemManaged, SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ this(matchRule, subscriberId, matchSubscriberIds,
+ wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
+ metered, roaming, defaultNetwork, subType, oemManaged,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
+ /** @hide */
public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
- String networkId, int metered, int roaming, int defaultNetwork, int subType,
- int oemManaged, int subscriberIdMatchRule) {
+ String[] matchWifiNetworkKeys, int metered, int roaming,
+ int defaultNetwork, int subType, int oemManaged, int subscriberIdMatchRule) {
+ Objects.requireNonNull(matchWifiNetworkKeys);
mMatchRule = matchRule;
mSubscriberId = subscriberId;
// TODO: Check whether mMatchSubscriberIds = null or mMatchSubscriberIds = {null} when
// mSubscriberId is null
mMatchSubscriberIds = matchSubscriberIds;
- mNetworkId = networkId;
+ mMatchWifiNetworkKeys = matchWifiNetworkKeys;
mMetered = metered;
mRoaming = roaming;
mDefaultNetwork = defaultNetwork;
mSubType = subType;
mOemManaged = oemManaged;
mSubscriberIdMatchRule = subscriberIdMatchRule;
- checkValidSubscriberIdMatchRule();
+ checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule);
if (!isKnownMatchRule(matchRule)) {
throw new IllegalArgumentException("Unknown network template rule " + matchRule
+ " will not match any identity.");
@@ -350,7 +446,7 @@
mMatchRule = in.readInt();
mSubscriberId = in.readString();
mMatchSubscriberIds = in.createStringArray();
- mNetworkId = in.readString();
+ mMatchWifiNetworkKeys = in.createStringArray();
mMetered = in.readInt();
mRoaming = in.readInt();
mDefaultNetwork = in.readInt();
@@ -360,11 +456,11 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mMatchRule);
dest.writeString(mSubscriberId);
dest.writeStringArray(mMatchSubscriberIds);
- dest.writeString(mNetworkId);
+ dest.writeStringArray(mMatchWifiNetworkKeys);
dest.writeInt(mMetered);
dest.writeInt(mRoaming);
dest.writeInt(mDefaultNetwork);
@@ -390,9 +486,7 @@
builder.append(", matchSubscriberIds=").append(
Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds)));
}
- if (mNetworkId != null) {
- builder.append(", networkId=").append(mNetworkId);
- }
+ builder.append(", matchWifiNetworkKeys=").append(Arrays.toString(mMatchWifiNetworkKeys));
if (mMetered != METERED_ALL) {
builder.append(", metered=").append(NetworkStats.meteredToString(mMetered));
}
@@ -416,8 +510,8 @@
@Override
public int hashCode() {
- return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming,
- mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule);
+ return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys),
+ mMetered, mRoaming, mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule);
}
@Override
@@ -426,28 +520,29 @@
final NetworkTemplate other = (NetworkTemplate) obj;
return mMatchRule == other.mMatchRule
&& Objects.equals(mSubscriberId, other.mSubscriberId)
- && Objects.equals(mNetworkId, other.mNetworkId)
&& mMetered == other.mMetered
&& mRoaming == other.mRoaming
&& mDefaultNetwork == other.mDefaultNetwork
&& mSubType == other.mSubType
&& mOemManaged == other.mOemManaged
- && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule;
+ && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule
+ && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys);
}
return false;
}
- private String subscriberIdMatchRuleToString(int rule) {
+ private static String subscriberIdMatchRuleToString(int rule) {
switch (rule) {
- case SUBSCRIBER_ID_MATCH_RULE_EXACT:
+ case NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT:
return "EXACT_MATCH";
- case SUBSCRIBER_ID_MATCH_RULE_ALL:
+ case NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL:
return "ALL";
default:
return "Unknown rule " + rule;
}
}
+ /** @hide */
public boolean isMatchRuleMobile() {
switch (mMatchRule) {
case MATCH_MOBILE:
@@ -458,48 +553,107 @@
}
}
- public boolean isPersistable() {
+ /**
+ * Get match rule of the template. See {@code MATCH_*}.
+ */
+ @UnsupportedAppUsage
+ public int getMatchRule() {
+ // Wildcard rules are not exposed. For external callers, convert wildcard rules to
+ // exposed rules before returning.
switch (mMatchRule) {
case MATCH_MOBILE_WILDCARD:
+ return MATCH_MOBILE;
case MATCH_WIFI_WILDCARD:
- return false;
- case MATCH_CARRIER:
- return mSubscriberId != null;
- case MATCH_WIFI:
- if (Objects.equals(mNetworkId, WIFI_NETWORKID_ALL)
- && mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) {
- return false;
- }
- return true;
+ return MATCH_WIFI;
default:
- return true;
+ return mMatchRule;
}
}
- @UnsupportedAppUsage
- public int getMatchRule() {
- return mMatchRule;
- }
-
+ /**
+ * Get subscriber Id of the template.
+ */
+ @Nullable
@UnsupportedAppUsage
public String getSubscriberId() {
return mSubscriberId;
}
+ /**
+ * Get set of subscriber Ids of the template.
+ */
+ @NonNull
+ public Set<String> getSubscriberIds() {
+ return new ArraySet<>(Arrays.asList(mMatchSubscriberIds));
+ }
+
+ /**
+ * Get Wifi Network Key of the template. See {@link WifiInfo#getCurrentNetworkKey()}.
+ */
+ @Nullable
+ public String getWifiNetworkKey() {
+ return CollectionUtils.isEmpty(mMatchWifiNetworkKeys) ? null : mMatchWifiNetworkKeys[0];
+ }
+
+ /**
+ * Get set of Wifi Network Keys of the template.
+ */
+ @Nullable
+ public Set<String> getWifiNetworkKeys() {
+ return new ArraySet<>(Arrays.asList(mMatchWifiNetworkKeys));
+ }
+
+ /** @hide */
+ // TODO: Remove this and replace all callers with {@link #getWifiNetworkKey()}.
+ @Nullable
public String getNetworkId() {
- return mNetworkId;
+ return getWifiNetworkKey();
}
- public int getSubscriberIdMatchRule() {
- return mSubscriberIdMatchRule;
- }
-
+ /**
+ * Get meteredness filter of the template.
+ */
+ @NetworkStats.Meteredness
public int getMeteredness() {
return mMetered;
}
/**
+ * Get roaming filter of the template.
+ */
+ @NetworkStats.Roaming
+ public int getRoaming() {
+ return mRoaming;
+ }
+
+ /**
+ * Get the default network status filter of the template.
+ */
+ @NetworkStats.DefaultNetwork
+ public int getDefaultNetworkStatus() {
+ return mDefaultNetwork;
+ }
+
+ /**
+ * Get the Radio Access Technology(RAT) type filter of the template.
+ */
+ public int getRatType() {
+ return mSubType;
+ }
+
+ /**
+ * Get the OEM managed filter of the template. See {@code OEM_MANAGED_*} or
+ * {@code android.net.NetworkIdentity#OEM_*}.
+ */
+ @OemManaged
+ public int getOemManaged() {
+ return mOemManaged;
+ }
+
+ /**
* Test if given {@link NetworkIdentity} matches this template.
+ *
+ * @hide
*/
public boolean matches(NetworkIdentity ident) {
if (!matchesMetered(ident)) return false;
@@ -565,31 +719,38 @@
* Check if this template matches {@code subscriberId}. Returns true if this
* template was created with {@code SUBSCRIBER_ID_MATCH_RULE_ALL}, or with a
* {@code mMatchSubscriberIds} array that contains {@code subscriberId}.
+ *
+ * @hide
*/
public boolean matchesSubscriberId(@Nullable String subscriberId) {
- return mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL
- || ArrayUtils.contains(mMatchSubscriberIds, subscriberId);
+ return mSubscriberIdMatchRule == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+ || CollectionUtils.contains(mMatchSubscriberIds, subscriberId);
}
/**
- * Check if network with matching SSID. Returns true when the SSID matches, or when
- * {@code mNetworkId} is {@code WIFI_NETWORKID_ALL}.
+ * Check if network matches key of the wifi network.
+ * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is
+ * empty.
+ *
+ * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+ * to know details about the key.
*/
- private boolean matchesWifiNetworkId(@Nullable String networkId) {
- return Objects.equals(mNetworkId, WIFI_NETWORKID_ALL)
- || Objects.equals(sanitizeSsid(mNetworkId), sanitizeSsid(networkId));
+ private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) {
+ Objects.requireNonNull(wifiNetworkKey);
+ return CollectionUtils.isEmpty(mMatchWifiNetworkKeys)
+ || CollectionUtils.contains(mMatchWifiNetworkKeys, wifiNetworkKey);
}
/**
- * Check if mobile network with matching IMSI.
+ * Check if mobile network matches IMSI.
*/
private boolean matchesMobile(NetworkIdentity ident) {
if (ident.mType == TYPE_WIMAX) {
// TODO: consider matching against WiMAX subscriber identity
return true;
} else {
- return ident.mType == TYPE_MOBILE && !ArrayUtils.isEmpty(mMatchSubscriberIds)
- && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)
+ return ident.mType == TYPE_MOBILE && !CollectionUtils.isEmpty(mMatchSubscriberIds)
+ && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)
&& matchesCollapsedRatType(ident);
}
}
@@ -599,10 +760,13 @@
* The mapping is corresponding to {@code TelephonyManager#NETWORK_CLASS_BIT_MASK_*}.
*
* @param ratType An integer defined in {@code TelephonyManager#NETWORK_TYPE_*}.
+ *
+ * @hide
*/
// TODO: 1. Consider move this to TelephonyManager if used by other modules.
// 2. Consider make this configurable.
// 3. Use TelephonyManager APIs when available.
+ // TODO: @SystemApi when ready.
public static int getCollapsedRatType(int ratType) {
switch (ratType) {
case TelephonyManager.NETWORK_TYPE_GPRS:
@@ -639,7 +803,10 @@
/**
* Return all supported collapsed RAT types that could be returned by
* {@link #getCollapsedRatType(int)}.
+ *
+ * @hide
*/
+ // TODO: @SystemApi when ready.
@NonNull
public static final int[] getAllCollapsedRatTypes() {
final int[] ratTypes = TelephonyManager.getAllNetworkTypes();
@@ -673,7 +840,7 @@
switch (ident.mType) {
case TYPE_WIFI:
return matchesSubscriberId(ident.mSubscriberId)
- && matchesWifiNetworkId(ident.mNetworkId);
+ && matchesWifiNetworkKey(ident.mNetworkId);
default:
return false;
}
@@ -694,8 +861,8 @@
*/
private boolean matchesCarrier(NetworkIdentity ident) {
return ident.mSubscriberId != null
- && !ArrayUtils.isEmpty(mMatchSubscriberIds)
- && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
+ && !CollectionUtils.isEmpty(mMatchSubscriberIds)
+ && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
}
private boolean matchesMobileWildcard(NetworkIdentity ident) {
@@ -779,6 +946,8 @@
* active merge set [A,B], we'd return a new template that primarily matches
* A, but also matches B.
* TODO: remove and use {@link #normalize(NetworkTemplate, List)}.
+ *
+ * @hide
*/
@UnsupportedAppUsage
public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
@@ -797,7 +966,10 @@
* For example, given an incoming template matching B, and the currently
* active merge set [A,B], we'd return a new template that primarily matches
* A, but also matches B.
+ *
+ * @hide
*/
+ // TODO: @SystemApi when ready.
public static NetworkTemplate normalize(NetworkTemplate template, List<String[]> mergedList) {
// Now there are several types of network which uses SubscriberId to store network
// information. For instances:
@@ -807,11 +979,13 @@
if (template.mSubscriberId == null) return template;
for (String[] merged : mergedList) {
- if (ArrayUtils.contains(merged, template.mSubscriberId)) {
+ if (CollectionUtils.contains(merged, template.mSubscriberId)) {
// Requested template subscriber is part of the merge group; return
// a template that matches all merged subscribers.
+ final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys;
return new NetworkTemplate(template.mMatchRule, merged[0], merged,
- template.mNetworkId);
+ CollectionUtils.isEmpty(matchWifiNetworkKeys)
+ ? null : matchWifiNetworkKeys[0]);
}
}
@@ -830,4 +1004,206 @@
return new NetworkTemplate[size];
}
};
+
+ /**
+ * Builder class for NetworkTemplate.
+ */
+ public static final class Builder {
+ private final int mMatchRule;
+ // Use a SortedSet to provide a deterministic order when fetching the first one.
+ @NonNull
+ private final SortedSet<String> mMatchSubscriberIds =
+ new TreeSet<>(Comparator.nullsFirst(Comparator.naturalOrder()));
+ @NonNull
+ private final SortedSet<String> mMatchWifiNetworkKeys = new TreeSet<>();
+
+ // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
+ private int mMetered;
+ private int mRoaming;
+ private int mDefaultNetwork;
+ private int mRatType;
+
+ // Bitfield containing OEM network properties {@code NetworkIdentity#OEM_*}.
+ private int mOemManaged;
+
+ /**
+ * Creates a new Builder with given match rule to construct NetworkTemplate objects.
+ *
+ * @param matchRule the match rule of the template, see {@code MATCH_*}.
+ */
+ public Builder(@TemplateMatchRule final int matchRule) {
+ assertRequestableMatchRule(matchRule);
+ // Initialize members with default values.
+ mMatchRule = matchRule;
+ mMetered = METERED_ALL;
+ mRoaming = ROAMING_ALL;
+ mDefaultNetwork = DEFAULT_NETWORK_ALL;
+ mRatType = NETWORK_TYPE_ALL;
+ mOemManaged = OEM_MANAGED_ALL;
+ }
+
+ /**
+ * Set the Subscriber Ids. Calling this function with an empty set represents
+ * the intention of matching any Subscriber Ids.
+ *
+ * @param subscriberIds the list of Subscriber Ids.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setSubscriberIds(@NonNull Set<String> subscriberIds) {
+ Objects.requireNonNull(subscriberIds);
+ mMatchSubscriberIds.clear();
+ mMatchSubscriberIds.addAll(subscriberIds);
+ return this;
+ }
+
+ /**
+ * Set the Wifi Network Keys. Calling this function with an empty set represents
+ * the intention of matching any Wifi Network Key.
+ *
+ * @param wifiNetworkKeys the list of Wifi Network Key,
+ * see {@link WifiInfo#getCurrentNetworkKey()}.
+ * Or an empty list to match all networks.
+ * Note that {@code getCurrentNetworkKey()} might get null key
+ * when wifi disconnects. However, the caller should never invoke
+ * this function with a null Wifi Network Key since such statistics
+ * never exists.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setWifiNetworkKeys(@NonNull Set<String> wifiNetworkKeys) {
+ Objects.requireNonNull(wifiNetworkKeys);
+ for (String key : wifiNetworkKeys) {
+ if (key == null) {
+ throw new IllegalArgumentException("Null is not a valid key");
+ }
+ }
+ mMatchWifiNetworkKeys.clear();
+ mMatchWifiNetworkKeys.addAll(wifiNetworkKeys);
+ return this;
+ }
+
+ /**
+ * Set the meteredness filter.
+ *
+ * @param metered the meteredness filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setMeteredness(@NetworkStats.Meteredness int metered) {
+ mMetered = metered;
+ return this;
+ }
+
+ /**
+ * Set the roaming filter.
+ *
+ * @param roaming the roaming filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setRoaming(@NetworkStats.Roaming int roaming) {
+ mRoaming = roaming;
+ return this;
+ }
+
+ /**
+ * Set the default network status filter.
+ *
+ * @param defaultNetwork the default network status filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setDefaultNetworkStatus(@NetworkStats.DefaultNetwork int defaultNetwork) {
+ mDefaultNetwork = defaultNetwork;
+ return this;
+ }
+
+ /**
+ * Set the Radio Access Technology(RAT) type filter.
+ *
+ * @param ratType the Radio Access Technology(RAT) type filter. Use
+ * {@link #NETWORK_TYPE_ALL} to include all network types when filtering.
+ * See {@code TelephonyManager.NETWORK_TYPE_*}.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setRatType(@NetworkType int ratType) {
+ // Input will be validated with the match rule when building the template.
+ mRatType = ratType;
+ return this;
+ }
+
+ /**
+ * Set the OEM managed filter.
+ *
+ * @param oemManaged the match rule to match different type of OEM managed network or
+ * unmanaged networks. See {@code OEM_MANAGED_*}.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setOemManaged(@OemManaged int oemManaged) {
+ mOemManaged = oemManaged;
+ return this;
+ }
+
+ /**
+ * Check whether the match rule is requestable.
+ *
+ * @param matchRule the target match rule to be checked.
+ */
+ private static void assertRequestableMatchRule(final int matchRule) {
+ if (!isKnownMatchRule(matchRule)
+ || matchRule == MATCH_PROXY
+ || matchRule == MATCH_MOBILE_WILDCARD
+ || matchRule == MATCH_WIFI_WILDCARD) {
+ throw new IllegalArgumentException("Invalid match rule: "
+ + getMatchRuleName(matchRule));
+ }
+ }
+
+ private void assertRequestableParameters() {
+ validateWifiNetworkKeys();
+ // TODO: Check all the input are legitimate.
+ }
+
+ private void validateWifiNetworkKeys() {
+ if (mMatchRule != MATCH_WIFI && !mMatchWifiNetworkKeys.isEmpty()) {
+ throw new IllegalArgumentException("Trying to build non wifi match rule: "
+ + mMatchRule + " with wifi network keys");
+ }
+ }
+
+ /**
+ * For backward compatibility, deduce match rule to a wildcard match rule
+ * if the Subscriber Ids are empty.
+ */
+ private int getWildcardDeducedMatchRule() {
+ if (mMatchRule == MATCH_MOBILE && mMatchSubscriberIds.isEmpty()) {
+ return MATCH_MOBILE_WILDCARD;
+ } else if (mMatchRule == MATCH_WIFI && mMatchSubscriberIds.isEmpty()
+ && mMatchWifiNetworkKeys.isEmpty()) {
+ return MATCH_WIFI_WILDCARD;
+ }
+ return mMatchRule;
+ }
+
+ /**
+ * Builds the instance of the NetworkTemplate.
+ *
+ * @return the built instance of NetworkTemplate.
+ */
+ @NonNull
+ public NetworkTemplate build() {
+ assertRequestableParameters();
+ final int subscriberIdMatchRule = mMatchSubscriberIds.isEmpty()
+ ? NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+ : NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
+ return new NetworkTemplate(getWildcardDeducedMatchRule(),
+ mMatchSubscriberIds.isEmpty() ? null : mMatchSubscriberIds.iterator().next(),
+ mMatchSubscriberIds.toArray(new String[0]),
+ mMatchWifiNetworkKeys.toArray(new String[0]), mMetered, mRoaming,
+ mDefaultNetwork, mRatType, mOemManaged, subscriberIdMatchRule);
+ }
+ }
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index fa65061..d8feb88 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -29,7 +29,6 @@
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.util.DataUnit;
import com.android.server.NetworkManagementSocketTagger;
@@ -59,19 +58,19 @@
*/
public final static int UNSUPPORTED = -1;
- /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
@Deprecated
public static final long KB_IN_BYTES = 1024;
- /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
@Deprecated
public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
- /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
@Deprecated
public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
- /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
@Deprecated
public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
- /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
@Deprecated
public static final long PB_IN_BYTES = TB_IN_BYTES * 1024;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
index 6c597e2..0f21e55 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,10 +16,6 @@
package android.net.nsd;
-import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.internal.util.Preconditions.checkStringNotEmpty;
-
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
@@ -32,11 +28,13 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Protocol;
+
+import java.util.Objects;
/**
* The Network Service Discovery Manager class provides the API to discover services
@@ -175,65 +173,63 @@
*/
public static final int NSD_STATE_ENABLED = 2;
- private static final int BASE = Protocol.BASE_NSD_MANAGER;
+ /** @hide */
+ public static final int DISCOVER_SERVICES = 1;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_STARTED = 2;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_FAILED = 3;
+ /** @hide */
+ public static final int SERVICE_FOUND = 4;
+ /** @hide */
+ public static final int SERVICE_LOST = 5;
/** @hide */
- public static final int DISCOVER_SERVICES = BASE + 1;
+ public static final int STOP_DISCOVERY = 6;
/** @hide */
- public static final int DISCOVER_SERVICES_STARTED = BASE + 2;
+ public static final int STOP_DISCOVERY_FAILED = 7;
/** @hide */
- public static final int DISCOVER_SERVICES_FAILED = BASE + 3;
- /** @hide */
- public static final int SERVICE_FOUND = BASE + 4;
- /** @hide */
- public static final int SERVICE_LOST = BASE + 5;
+ public static final int STOP_DISCOVERY_SUCCEEDED = 8;
/** @hide */
- public static final int STOP_DISCOVERY = BASE + 6;
+ public static final int REGISTER_SERVICE = 9;
/** @hide */
- public static final int STOP_DISCOVERY_FAILED = BASE + 7;
+ public static final int REGISTER_SERVICE_FAILED = 10;
/** @hide */
- public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 8;
+ public static final int REGISTER_SERVICE_SUCCEEDED = 11;
/** @hide */
- public static final int REGISTER_SERVICE = BASE + 9;
+ public static final int UNREGISTER_SERVICE = 12;
/** @hide */
- public static final int REGISTER_SERVICE_FAILED = BASE + 10;
+ public static final int UNREGISTER_SERVICE_FAILED = 13;
/** @hide */
- public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11;
+ public static final int UNREGISTER_SERVICE_SUCCEEDED = 14;
/** @hide */
- public static final int UNREGISTER_SERVICE = BASE + 12;
+ public static final int RESOLVE_SERVICE = 15;
/** @hide */
- public static final int UNREGISTER_SERVICE_FAILED = BASE + 13;
+ public static final int RESOLVE_SERVICE_FAILED = 16;
/** @hide */
- public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14;
+ public static final int RESOLVE_SERVICE_SUCCEEDED = 17;
/** @hide */
- public static final int RESOLVE_SERVICE = BASE + 18;
- /** @hide */
- public static final int RESOLVE_SERVICE_FAILED = BASE + 19;
- /** @hide */
- public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 20;
+ public static final int DAEMON_CLEANUP = 18;
/** @hide */
- public static final int DAEMON_CLEANUP = BASE + 21;
+ public static final int DAEMON_STARTUP = 19;
/** @hide */
- public static final int DAEMON_STARTUP = BASE + 22;
+ public static final int ENABLE = 20;
+ /** @hide */
+ public static final int DISABLE = 21;
/** @hide */
- public static final int ENABLE = BASE + 24;
- /** @hide */
- public static final int DISABLE = BASE + 25;
+ public static final int NATIVE_DAEMON_EVENT = 22;
/** @hide */
- public static final int NATIVE_DAEMON_EVENT = BASE + 26;
-
+ public static final int REGISTER_CLIENT = 23;
/** @hide */
- public static final int REGISTER_CLIENT = BASE + 27;
- /** @hide */
- public static final int UNREGISTER_CLIENT = BASE + 28;
+ public static final int UNREGISTER_CLIENT = 24;
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -550,7 +546,9 @@
final int key;
synchronized (mMapLock) {
int valueIndex = mListenerMap.indexOfValue(listener);
- checkArgument(valueIndex == -1, "listener already in use");
+ if (valueIndex != -1) {
+ throw new IllegalArgumentException("listener already in use");
+ }
key = nextListenerKey();
mListenerMap.put(key, listener);
mServiceMap.put(key, s);
@@ -569,7 +567,9 @@
checkListener(listener);
synchronized (mMapLock) {
int valueIndex = mListenerMap.indexOfValue(listener);
- checkArgument(valueIndex != -1, "listener not registered");
+ if (valueIndex == -1) {
+ throw new IllegalArgumentException("listener not registered");
+ }
return mListenerMap.keyAt(valueIndex);
}
}
@@ -598,7 +598,9 @@
*/
public void registerService(NsdServiceInfo serviceInfo, int protocolType,
RegistrationListener listener) {
- checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
+ if (serviceInfo.getPort() <= 0) {
+ throw new IllegalArgumentException("Invalid port number");
+ }
checkServiceInfo(serviceInfo);
checkProtocol(protocolType);
int key = putListener(listener, serviceInfo);
@@ -660,7 +662,9 @@
* Cannot be null. Cannot be in use for an active service discovery.
*/
public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
- checkStringNotEmpty(serviceType, "Service type cannot be empty");
+ if (TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
checkProtocol(protocolType);
NsdServiceInfo s = new NsdServiceInfo();
@@ -719,16 +723,22 @@
}
private static void checkListener(Object listener) {
- checkNotNull(listener, "listener cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
}
private static void checkProtocol(int protocolType) {
- checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
+ if (protocolType != PROTOCOL_DNS_SD) {
+ throw new IllegalArgumentException("Unsupported protocol");
+ }
}
private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
- checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
- checkStringNotEmpty(serviceInfo.getServiceName(), "Service name cannot be empty");
- checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
+ Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
+ if (TextUtils.isEmpty(serviceInfo.getServiceName())) {
+ throw new IllegalArgumentException("Service name cannot be empty");
+ }
+ if (TextUtils.isEmpty(serviceInfo.getServiceType())) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
}
}
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 6a64910..97dfb64 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -48,16 +48,42 @@
],
}
+// IpSec related libraries.
+
+filegroup {
+ name: "services.connectivity-ipsec-sources",
+ srcs: [
+ "src/com/android/server/IpSecService.java",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+// Ethernet related libraries.
+
+filegroup {
+ name: "services.connectivity-ethernet-sources",
+ srcs: [
+ "src/com/android/server/net/IpConfigStore.java",
+ ],
+ path: "src",
+ visibility: [
+ "//frameworks/opt/net/ethernet",
+ ],
+}
+
// Connectivity-T common libraries.
filegroup {
name: "services.connectivity-tiramisu-sources",
srcs: [
+ ":services.connectivity-ethernet-sources",
+ ":services.connectivity-ipsec-sources",
":services.connectivity-netstats-sources",
":services.connectivity-nsd-sources",
],
path: "src",
- visibility: [
- "//frameworks/base/services/core",
- ],
-}
\ No newline at end of file
+ visibility: ["//frameworks/base/services/core"],
+}
diff --git a/services/core/java/com/android/server/IpSecService.java b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
similarity index 90%
rename from services/core/java/com/android/server/IpSecService.java
rename to packages/ConnectivityT/service/src/com/android/server/IpSecService.java
index aeb8143..d1e432e 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
@@ -45,7 +45,6 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.TrafficStats;
-import android.net.util.NetdService;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -96,8 +95,6 @@
public class IpSecService extends IIpSecService.Stub {
private static final String TAG = "IpSecService";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String NETD_SERVICE_NAME = "netd";
private static final int[] ADDRESS_FAMILIES =
new int[] {OsConstants.AF_INET, OsConstants.AF_INET6};
@@ -106,6 +103,8 @@
@VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
+ private final INetd mNetd;
+
static {
try {
INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
@@ -119,6 +118,7 @@
/* Binder context for this service */
private final Context mContext;
+ private final Dependencies mDeps;
/**
* The next non-repeating global ID for tracking resources between users, this service, and
@@ -129,23 +129,24 @@
@GuardedBy("IpSecService.this")
private int mNextResourceId = 1;
- interface IpSecServiceConfiguration {
- INetd getNetdInstance() throws RemoteException;
-
- static IpSecServiceConfiguration GETSRVINSTANCE =
- new IpSecServiceConfiguration() {
- @Override
- public INetd getNetdInstance() throws RemoteException {
- final INetd netd = NetdService.getInstance();
- if (netd == null) {
- throw new RemoteException("Failed to Get Netd Instance");
- }
- return netd;
- }
- };
+ /**
+ * Dependencies of IpSecService, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * Get a reference to INetd.
+ */
+ public INetd getNetdInstance(Context context) throws RemoteException {
+ final INetd netd = INetd.Stub.asInterface((IBinder)
+ context.getSystemService(Context.NETD_SERVICE));
+ if (netd == null) {
+ throw new RemoteException("Failed to Get Netd Instance");
+ }
+ return netd;
+ }
}
- private final IpSecServiceConfiguration mSrvConfig;
final UidFdTagger mUidFdTagger;
/**
@@ -491,8 +492,8 @@
* <p>This class associates kernel resources with the UID that owns and controls them.
*/
private abstract class OwnedResourceRecord implements IResource {
- final int pid;
- final int uid;
+ final int mPid;
+ final int mUid;
protected final int mResourceId;
OwnedResourceRecord(int resourceId) {
@@ -501,8 +502,8 @@
throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
}
mResourceId = resourceId;
- pid = Binder.getCallingPid();
- uid = Binder.getCallingUid();
+ mPid = Binder.getCallingPid();
+ mUid = Binder.getCallingUid();
getResourceTracker().take();
}
@@ -512,7 +513,7 @@
/** Convenience method; retrieves the user resource record for the stored UID. */
protected UserRecord getUserRecord() {
- return mUserResourceTracker.getUserRecord(uid);
+ return mUserResourceTracker.getUserRecord(mUid);
}
@Override
@@ -527,9 +528,9 @@
.append("{mResourceId=")
.append(mResourceId)
.append(", pid=")
- .append(pid)
+ .append(mPid)
.append(", uid=")
- .append(uid)
+ .append(mUid)
.append("}")
.toString();
}
@@ -545,7 +546,7 @@
SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
private final String mTypeName;
- public RefcountedResourceArray(String typeName) {
+ RefcountedResourceArray(String typeName) {
this.mTypeName = typeName;
}
@@ -625,16 +626,14 @@
public void freeUnderlyingResources() {
int spi = mSpi.getSpi();
try {
- mSrvConfig
- .getNetdInstance()
- .ipSecDeleteSecurityAssociation(
- uid,
- mConfig.getSourceAddress(),
- mConfig.getDestinationAddress(),
- spi,
- mConfig.getMarkValue(),
- mConfig.getMarkMask(),
- mConfig.getXfrmInterfaceId());
+ mNetd.ipSecDeleteSecurityAssociation(
+ mUid,
+ mConfig.getSourceAddress(),
+ mConfig.getDestinationAddress(),
+ spi,
+ mConfig.getMarkValue(),
+ mConfig.getMarkMask(),
+ mConfig.getXfrmInterfaceId());
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Failed to delete SA with ID: " + mResourceId, e);
}
@@ -681,7 +680,8 @@
private boolean mOwnedByTransform = false;
- SpiRecord(int resourceId, String sourceAddress, String destinationAddress, int spi) {
+ SpiRecord(int resourceId, String sourceAddress,
+ String destinationAddress, int spi) {
super(resourceId);
mSourceAddress = sourceAddress;
mDestinationAddress = destinationAddress;
@@ -693,11 +693,9 @@
public void freeUnderlyingResources() {
try {
if (!mOwnedByTransform) {
- mSrvConfig
- .getNetdInstance()
- .ipSecDeleteSecurityAssociation(
- uid, mSourceAddress, mDestinationAddress, mSpi, 0 /* mark */,
- 0 /* mask */, 0 /* if_id */);
+ mNetd.ipSecDeleteSecurityAssociation(
+ mUid, mSourceAddress, mDestinationAddress, mSpi, 0 /* mark */,
+ 0 /* mask */, 0 /* if_id */);
}
} catch (ServiceSpecificException | RemoteException e) {
Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId, e);
@@ -844,19 +842,18 @@
// Teardown VTI
// Delete global policies
try {
- final INetd netd = mSrvConfig.getNetdInstance();
- netd.ipSecRemoveTunnelInterface(mInterfaceName);
+ mNetd.ipSecRemoveTunnelInterface(mInterfaceName);
for (int selAddrFamily : ADDRESS_FAMILIES) {
- netd.ipSecDeleteSecurityPolicy(
- uid,
+ mNetd.ipSecDeleteSecurityPolicy(
+ mUid,
selAddrFamily,
IpSecManager.DIRECTION_OUT,
mOkey,
0xffffffff,
mIfId);
- netd.ipSecDeleteSecurityPolicy(
- uid,
+ mNetd.ipSecDeleteSecurityPolicy(
+ mUid,
selAddrFamily,
IpSecManager.DIRECTION_IN,
mIkey,
@@ -1012,29 +1009,28 @@
* @param context Binder context for this service
*/
private IpSecService(Context context) {
- this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
+ this(context, new Dependencies());
}
static IpSecService create(Context context)
throws InterruptedException {
final IpSecService service = new IpSecService(context);
- service.connectNativeNetdService();
return service;
}
@NonNull
private AppOpsManager getAppOpsManager() {
AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- if(appOps == null) throw new RuntimeException("System Server couldn't get AppOps");
+ if (appOps == null) throw new RuntimeException("System Server couldn't get AppOps");
return appOps;
}
/** @hide */
@VisibleForTesting
- public IpSecService(Context context, IpSecServiceConfiguration config) {
+ public IpSecService(Context context, Dependencies deps) {
this(
context,
- config,
+ deps,
(fd, uid) -> {
try {
TrafficStats.setThreadStatsUid(uid);
@@ -1047,13 +1043,18 @@
/** @hide */
@VisibleForTesting
- public IpSecService(Context context, IpSecServiceConfiguration config,
- UidFdTagger uidFdTagger) {
+ public IpSecService(Context context, Dependencies deps, UidFdTagger uidFdTagger) {
mContext = context;
- mSrvConfig = config;
+ mDeps = Objects.requireNonNull(deps, "Missing dependencies.");
mUidFdTagger = uidFdTagger;
+ try {
+ mNetd = mDeps.getNetdInstance(mContext);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
+ /** Called by system server when system is ready. */
public void systemReady() {
if (isNetdAlive()) {
Log.d(TAG, "IpSecService is ready");
@@ -1062,25 +1063,12 @@
}
}
- private void connectNativeNetdService() {
- // Avoid blocking the system server to do this
- new Thread() {
- @Override
- public void run() {
- synchronized (IpSecService.this) {
- NetdService.get(NETD_FETCH_TIMEOUT_MS);
- }
- }
- }.start();
- }
-
synchronized boolean isNetdAlive() {
try {
- final INetd netd = mSrvConfig.getNetdInstance();
- if (netd == null) {
+ if (mNetd == null) {
return false;
}
- return netd.isAlive();
+ return mNetd.isAlive();
} catch (RemoteException re) {
return false;
}
@@ -1141,15 +1129,13 @@
IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
}
- spi =
- mSrvConfig
- .getNetdInstance()
- .ipSecAllocateSpi(callingUid, "", destinationAddress, requestedSpi);
+ spi = mNetd.ipSecAllocateSpi(callingUid, "", destinationAddress, requestedSpi);
Log.d(TAG, "Allocated SPI " + spi);
userRecord.mSpiRecords.put(
resourceId,
new RefcountedResource<SpiRecord>(
- new SpiRecord(resourceId, "", destinationAddress, spi), binder));
+ new SpiRecord(resourceId, "",
+ destinationAddress, spi), binder));
} catch (ServiceSpecificException e) {
if (e.errorCode == OsConstants.ENOENT) {
return new IpSecSpiResponse(
@@ -1229,7 +1215,7 @@
* <p>Since the socket is created on behalf of an unprivileged application, all traffic
* should be accounted to the UID of the unprivileged application.
*/
- public void tag(FileDescriptor fd, int uid) throws IOException;
+ void tag(FileDescriptor fd, int uid) throws IOException;
}
/**
@@ -1266,8 +1252,7 @@
OsConstants.UDP_ENCAP,
OsConstants.UDP_ENCAP_ESPINUDP);
- mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(
- new ParcelFileDescriptor(sockFd), callingUid);
+ mNetd.ipSecSetEncapSocketOwner(new ParcelFileDescriptor(sockFd), callingUid);
if (port != 0) {
Log.v(TAG, "Binding to port " + port);
Os.bind(sockFd, INADDR_ANY, port);
@@ -1329,16 +1314,15 @@
// Create VTI
// Add inbound/outbound global policies
// (use reqid = 0)
- final INetd netd = mSrvConfig.getNetdInstance();
- netd.ipSecAddTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey, resourceId);
+ mNetd.ipSecAddTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey, resourceId);
BinderUtils.withCleanCallingIdentity(() -> {
- NetdUtils.setInterfaceUp(netd, intfName);
+ NetdUtils.setInterfaceUp(mNetd, intfName);
});
for (int selAddrFamily : ADDRESS_FAMILIES) {
// Always send down correct local/remote addresses for template.
- netd.ipSecAddSecurityPolicy(
+ mNetd.ipSecAddSecurityPolicy(
callerUid,
selAddrFamily,
IpSecManager.DIRECTION_OUT,
@@ -1348,7 +1332,7 @@
okey,
0xffffffff,
resourceId);
- netd.ipSecAddSecurityPolicy(
+ mNetd.ipSecAddSecurityPolicy(
callerUid,
selAddrFamily,
IpSecManager.DIRECTION_IN,
@@ -1368,7 +1352,7 @@
//
// This is necessary only on the tunnel interface, and not any the interface to
// which traffic will be forwarded to.
- netd.ipSecAddSecurityPolicy(
+ mNetd.ipSecAddSecurityPolicy(
callerUid,
selAddrFamily,
IpSecManager.DIRECTION_FWD,
@@ -1425,12 +1409,10 @@
try {
// We can assume general validity of the IP address, since we get them as a
// LinkAddress, which does some validation.
- mSrvConfig
- .getNetdInstance()
- .interfaceAddAddress(
- tunnelInterfaceInfo.mInterfaceName,
- localAddr.getAddress().getHostAddress(),
- localAddr.getPrefixLength());
+ mNetd.interfaceAddAddress(
+ tunnelInterfaceInfo.mInterfaceName,
+ localAddr.getAddress().getHostAddress(),
+ localAddr.getPrefixLength());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1454,9 +1436,7 @@
try {
// We can assume general validity of the IP address, since we get them as a
// LinkAddress, which does some validation.
- mSrvConfig
- .getNetdInstance()
- .interfaceDelAddress(
+ mNetd.interfaceDelAddress(
tunnelInterfaceInfo.mInterfaceName,
localAddr.getAddress().getHostAddress(),
localAddr.getPrefixLength());
@@ -1669,30 +1649,28 @@
cryptName = crypt.getName();
}
- mSrvConfig
- .getNetdInstance()
- .ipSecAddSecurityAssociation(
- Binder.getCallingUid(),
- c.getMode(),
- c.getSourceAddress(),
- c.getDestinationAddress(),
- (c.getNetwork() != null) ? c.getNetwork().getNetId() : 0,
- spiRecord.getSpi(),
- c.getMarkValue(),
- c.getMarkMask(),
- (auth != null) ? auth.getName() : "",
- (auth != null) ? auth.getKey() : new byte[] {},
- (auth != null) ? auth.getTruncationLengthBits() : 0,
- cryptName,
- (crypt != null) ? crypt.getKey() : new byte[] {},
- (crypt != null) ? crypt.getTruncationLengthBits() : 0,
- (authCrypt != null) ? authCrypt.getName() : "",
- (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
- (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
- encapType,
- encapLocalPort,
- encapRemotePort,
- c.getXfrmInterfaceId());
+ mNetd.ipSecAddSecurityAssociation(
+ Binder.getCallingUid(),
+ c.getMode(),
+ c.getSourceAddress(),
+ c.getDestinationAddress(),
+ (c.getNetwork() != null) ? c.getNetwork().getNetId() : 0,
+ spiRecord.getSpi(),
+ c.getMarkValue(),
+ c.getMarkMask(),
+ (auth != null) ? auth.getName() : "",
+ (auth != null) ? auth.getKey() : new byte[] {},
+ (auth != null) ? auth.getTruncationLengthBits() : 0,
+ cryptName,
+ (crypt != null) ? crypt.getKey() : new byte[] {},
+ (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+ (authCrypt != null) ? authCrypt.getName() : "",
+ (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+ (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
+ encapType,
+ encapLocalPort,
+ encapRemotePort,
+ c.getXfrmInterfaceId());
}
/**
@@ -1771,7 +1749,7 @@
TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
// TODO: make this a function.
- if (info.pid != getCallingPid() || info.uid != callingUid) {
+ if (info.mPid != getCallingPid() || info.mUid != callingUid) {
throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
}
@@ -1781,15 +1759,13 @@
c.getMode() == IpSecTransform.MODE_TRANSPORT,
"Transform mode was not Transport mode; cannot be applied to a socket");
- mSrvConfig
- .getNetdInstance()
- .ipSecApplyTransportModeTransform(
- socket,
- callingUid,
- direction,
- c.getSourceAddress(),
- c.getDestinationAddress(),
- info.getSpiRecord().getSpi());
+ mNetd.ipSecApplyTransportModeTransform(
+ socket,
+ callingUid,
+ direction,
+ c.getSourceAddress(),
+ c.getDestinationAddress(),
+ info.getSpiRecord().getSpi());
}
/**
@@ -1801,9 +1777,7 @@
@Override
public synchronized void removeTransportModeTransforms(ParcelFileDescriptor socket)
throws RemoteException {
- mSrvConfig
- .getNetdInstance()
- .ipSecRemoveTransportModeTransform(socket);
+ mNetd.ipSecRemoveTransportModeTransform(socket);
}
/**
@@ -1878,18 +1852,16 @@
// Always update the policy with the relevant XFRM_IF_ID
for (int selAddrFamily : ADDRESS_FAMILIES) {
- mSrvConfig
- .getNetdInstance()
- .ipSecUpdateSecurityPolicy(
- callingUid,
- selAddrFamily,
- direction,
- transformInfo.getConfig().getSourceAddress(),
- transformInfo.getConfig().getDestinationAddress(),
- spi, // If outbound, also add SPI to the policy.
- mark, // Must always set policy mark; ikey/okey for VTIs
- 0xffffffff,
- c.getXfrmInterfaceId());
+ mNetd.ipSecUpdateSecurityPolicy(
+ callingUid,
+ selAddrFamily,
+ direction,
+ transformInfo.getConfig().getSourceAddress(),
+ transformInfo.getConfig().getDestinationAddress(),
+ spi, // If outbound, also add SPI to the policy.
+ mark, // Must always set policy mark; ikey/okey for VTIs
+ 0xffffffff,
+ c.getXfrmInterfaceId());
}
// Update SA with tunnel mark (ikey or okey based on direction)
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java
similarity index 95%
rename from services/core/java/com/android/server/net/IpConfigStore.java
rename to packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java
index d17dbde..3a9a544 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java
@@ -44,6 +44,9 @@
import java.util.ArrayList;
import java.util.List;
+/**
+ * This class provides an API to store and manage L3 network IP configuration.
+ */
public class IpConfigStore {
private static final String TAG = "IpConfigStore";
private static final boolean DBG = false;
@@ -78,6 +81,9 @@
return writeConfig(out, configKey, config, IPCONFIG_FILE_VERSION);
}
+ /**
+ * Write the IP configuration with the given parameters to {@link DataOutputStream}.
+ */
@VisibleForTesting
public static boolean writeConfig(DataOutputStream out, String configKey,
IpConfiguration config, int version) throws IOException {
@@ -154,10 +160,10 @@
break;
case UNASSIGNED:
/* Ignore */
- break;
- default:
- loge("Ignore invalid proxy settings while writing");
- break;
+ break;
+ default:
+ loge("Ignore invalid proxy settings while writing");
+ break;
}
if (written) {
@@ -177,7 +183,7 @@
}
/**
- * @Deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead.
+ * @deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead.
* New method uses string as network identifier which could be interface name or MAC address or
* other token.
*/
@@ -186,22 +192,28 @@
final SparseArray<IpConfiguration> networks) {
mWriter.write(filePath, out -> {
out.writeInt(IPCONFIG_FILE_VERSION);
- for(int i = 0; i < networks.size(); i++) {
+ for (int i = 0; i < networks.size(); i++) {
writeConfig(out, String.valueOf(networks.keyAt(i)), networks.valueAt(i));
}
});
}
+ /**
+ * Write the IP configuration associated to the target networks to the destination path.
+ */
public void writeIpConfigurations(String filePath,
ArrayMap<String, IpConfiguration> networks) {
mWriter.write(filePath, out -> {
out.writeInt(IPCONFIG_FILE_VERSION);
- for(int i = 0; i < networks.size(); i++) {
+ for (int i = 0; i < networks.size(); i++) {
writeConfig(out, networks.keyAt(i), networks.valueAt(i));
}
});
}
+ /**
+ * Read the IP configuration from the destination path to {@link BufferedInputStream}.
+ */
public static ArrayMap<String, IpConfiguration> readIpConfigurations(String filePath) {
BufferedInputStream bufferedInputStream;
try {
@@ -215,7 +227,7 @@
return readIpConfigurations(bufferedInputStream);
}
- /** @Deprecated use {@link #readIpConfigurations(String)} */
+ /** @deprecated use {@link #readIpConfigurations(String)} */
@Deprecated
public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
BufferedInputStream bufferedInputStream;
@@ -230,7 +242,7 @@
return readIpAndProxyConfigurations(bufferedInputStream);
}
- /** @Deprecated use {@link #readIpConfigurations(InputStream)} */
+ /** @deprecated use {@link #readIpConfigurations(InputStream)} */
@Deprecated
public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(
InputStream inputStream) {
@@ -420,7 +432,7 @@
if (in != null) {
try {
in.close();
- } catch (Exception e) {}
+ } catch (Exception e) { }
}
}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index e6433db..bb123a3 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -24,19 +24,19 @@
import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.INetd;
import android.net.NetworkStats;
import android.net.UnderlyingNetworkInfo;
-import android.net.util.NetdService;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ProcFileReader;
+import com.android.net.module.util.CollectionUtils;
import libcore.io.IoUtils;
@@ -70,7 +70,7 @@
private final boolean mUseBpfStats;
- private INetd mNetdService;
+ private final INetd mNetd;
/**
* Guards persistent data access in this class
@@ -158,12 +158,12 @@
NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
}
- public NetworkStatsFactory() {
- this(new File("/proc/"), true);
+ public NetworkStatsFactory(@NonNull INetd netd) {
+ this(new File("/proc/"), true, netd);
}
@VisibleForTesting
- public NetworkStatsFactory(File procRoot, boolean useBpfStats) {
+ public NetworkStatsFactory(File procRoot, boolean useBpfStats, @NonNull INetd netd) {
mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
@@ -172,6 +172,7 @@
mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
}
+ mNetd = netd;
}
public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -298,10 +299,7 @@
// Ask netd to do a active map stats swap. When the binder call successfully returns,
// the system server should be able to safely read and clean the inactive map
// without race problem.
- if (mNetdService == null) {
- mNetdService = NetdService.getInstance();
- }
- mNetdService.trafficSwapActiveStatsMap();
+ mNetd.trafficSwapActiveStatsMap();
}
/**
@@ -434,7 +432,7 @@
entry.txBytes = reader.nextLong();
entry.txPackets = reader.nextLong();
- if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
+ if ((limitIfaces == null || CollectionUtils.contains(limitIfaces, entry.iface))
&& (limitUid == UID_ALL || limitUid == entry.uid)
&& (limitTag == TAG_ALL || limitTag == entry.tag)) {
stats.insertEntry(entry);
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
index 1a0866d..b57a4f9 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
@@ -18,8 +18,6 @@
import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
-import static com.android.internal.util.Preconditions.checkArgument;
-
import android.app.usage.NetworkStatsManager;
import android.net.DataUsageRequest;
import android.net.NetworkIdentitySet;
@@ -38,7 +36,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
-import android.util.Slog;
+import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -83,7 +81,7 @@
RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
accessLevel);
- if (LOGV) Slog.v(TAG, "Registering observer for " + request);
+ if (LOGV) Log.v(TAG, "Registering observer for " + request);
getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
return request;
}
@@ -116,7 +114,7 @@
if (mHandler == null) {
synchronized (this) {
if (mHandler == null) {
- if (LOGV) Slog.v(TAG, "Creating handler");
+ if (LOGV) Log.v(TAG, "Creating handler");
mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
}
}
@@ -172,15 +170,15 @@
RequestInfo requestInfo;
requestInfo = mDataUsageRequests.get(request.requestId);
if (requestInfo == null) {
- if (LOGV) Slog.v(TAG, "Trying to unregister unknown request " + request);
+ if (LOGV) Log.v(TAG, "Trying to unregister unknown request " + request);
return;
}
if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
- Slog.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
+ Log.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
return;
}
- if (LOGV) Slog.v(TAG, "Unregistering " + request);
+ if (LOGV) Log.v(TAG, "Unregistering " + request);
mDataUsageRequests.remove(request.requestId);
requestInfo.unlinkDeathRecipient();
requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
@@ -201,7 +199,7 @@
// Cap the minimum threshold to a safe default to avoid too many callbacks
long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
if (thresholdInBytes < request.thresholdInBytes) {
- Slog.w(TAG, "Threshold was too low for " + request
+ Log.w(TAG, "Threshold was too low for " + request
+ ". Overriding to a safer default of " + thresholdInBytes + " bytes");
}
return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
@@ -216,7 +214,10 @@
accessLevel);
} else {
// Safety check in case a new access level is added and we forgot to update this
- checkArgument(accessLevel >= NetworkStatsAccess.Level.DEVICESUMMARY);
+ if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) {
+ throw new IllegalArgumentException(
+ "accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
+ }
return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
accessLevel);
}
@@ -255,8 +256,9 @@
@Override
public void binderDied() {
- if (LOGV) Slog.v(TAG, "RequestInfo binderDied("
- + mRequest + ", " + mBinder + ")");
+ if (LOGV) {
+ Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mBinder + ")");
+ }
mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
callCallback(NetworkStatsManager.CALLBACK_RELEASED);
}
@@ -299,13 +301,13 @@
msg.setData(bundle);
try {
if (LOGV) {
- Slog.v(TAG, "sending notification " + callbackTypeToName(callbackType)
+ Log.v(TAG, "sending notification " + callbackTypeToName(callbackType)
+ " for " + mRequest);
}
mMessenger.send(msg);
} catch (RemoteException e) {
// May occur naturally in the race of binder death.
- Slog.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
+ Log.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
}
}
@@ -341,7 +343,7 @@
protected boolean checkStats() {
long bytesSoFar = getTotalBytesForNetwork(mRequest.template);
if (LOGV) {
- Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
+ Log.v(TAG, bytesSoFar + " bytes so far since notification for "
+ mRequest.template);
}
if (bytesSoFar > mRequest.thresholdInBytes) {
@@ -416,7 +418,7 @@
return history.getTotalBytes();
} catch (SecurityException e) {
if (LOGV) {
- Slog.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
+ Log.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
+ uid);
}
return 0;
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
index 5e27c77..c371f08 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
@@ -32,15 +32,12 @@
import android.os.Binder;
import android.os.DropBoxManager;
import android.service.NetworkStatsRecorderProto;
+import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.util.MathUtils;
-import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FileRotator;
-import com.android.internal.util.IndentingPrintWriter;
-
-import com.google.android.collect.Sets;
+import com.android.net.module.util.NetworkStatsUtils;
import libcore.io.IoUtils;
@@ -132,8 +129,8 @@
}
public void setPersistThreshold(long thresholdBytes) {
- if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
- mPersistThresholdBytes = MathUtils.constrain(
+ if (LOGV) Log.v(TAG, "setPersistThreshold() with " + thresholdBytes);
+ mPersistThresholdBytes = NetworkStatsUtils.constrain(
thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
}
@@ -185,7 +182,7 @@
}
private NetworkStatsCollection loadLocked(long start, long end) {
- if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie);
+ if (LOGD) Log.d(TAG, "loadLocked() reading from disk for " + mCookie);
final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
try {
mRotator.readMatching(res, start, end);
@@ -207,7 +204,7 @@
*/
public void recordSnapshotLocked(NetworkStats snapshot,
Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
- final HashSet<String> unknownIfaces = Sets.newHashSet();
+ final HashSet<String> unknownIfaces = new HashSet<>();
// skip recording when snapshot missing
if (snapshot == null) return;
@@ -272,7 +269,7 @@
mLastSnapshot = snapshot;
if (LOGV && unknownIfaces.size() > 0) {
- Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
+ Log.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
}
}
@@ -296,7 +293,7 @@
public void forcePersistLocked(long currentTimeMillis) {
Objects.requireNonNull(mRotator, "missing FileRotator");
if (mPending.isDirty()) {
- if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
+ if (LOGD) Log.d(TAG, "forcePersistLocked() writing for " + mCookie);
try {
mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
mRotator.maybeRotate(currentTimeMillis);
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 2beca73..ef84ce0 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -25,7 +25,6 @@
import static android.content.Intent.EXTRA_UID;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkIdentity.SUBTYPE_COMBINED;
-import static android.net.NetworkStack.checkNetworkStackPermission;
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.IFACE_VT;
@@ -90,6 +89,7 @@
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.DataUsageRequest;
+import android.net.INetd;
import android.net.INetworkManagementEventObserver;
import android.net.INetworkStatsService;
import android.net.INetworkStatsSession;
@@ -141,28 +141,29 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.util.MathUtils;
-import android.util.Slog;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
import com.android.internal.util.FileRotator;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetworkStatsUtils;
+import com.android.net.module.util.PermissionUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Clock;
import java.time.ZoneOffset;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -410,10 +411,11 @@
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
+ final INetd netd = INetd.Stub.asInterface(
+ (IBinder) context.getSystemService(Context.NETD_SERVICE));
final NetworkStatsService service = new NetworkStatsService(context, networkManager,
alarmManager, wakeLock, getDefaultClock(),
- new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(),
+ new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd),
new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
new Dependencies());
service.registerLocalService();
@@ -646,7 +648,7 @@
try {
mNetworkManager.setGlobalAlert(mGlobalAlertBytes);
} catch (IllegalStateException e) {
- Slog.w(TAG, "problem registering for global alert: " + e);
+ Log.w(TAG, "problem registering for global alert: " + e);
} catch (RemoteException e) {
// ignored; service lives in system_server
}
@@ -655,8 +657,6 @@
@Override
public INetworkStatsSession openSession() {
- // NOTE: if callers want to get non-augmented data, they should go
- // through the public API
return openSessionInternal(NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN, null);
}
@@ -766,7 +766,7 @@
return stats;
} catch (NullPointerException e) {
// TODO: Track down and fix the cause of this crash and remove this catch block.
- Slog.wtf(TAG, "NullPointerException in getSummaryForAllUid", e);
+ Log.wtf(TAG, "NullPointerException in getSummaryForAllUid", e);
throw e;
}
}
@@ -811,7 +811,7 @@
private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) {
return NetworkStatsAccess.checkAccessLevel(
- mContext, Binder.getCallingUid(), callingPackage);
+ mContext, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage);
}
/**
@@ -823,7 +823,7 @@
SubscriptionPlan plan = null;
if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0
&& mSettings.getAugmentEnabled()) {
- if (LOGD) Slog.d(TAG, "Resolving plan for " + template);
+ if (LOGD) Log.d(TAG, "Resolving plan for " + template);
final long token = Binder.clearCallingIdentity();
try {
plan = LocalServices.getService(NetworkPolicyManagerInternal.class)
@@ -831,7 +831,7 @@
} finally {
Binder.restoreCallingIdentity(token);
}
- if (LOGD) Slog.d(TAG, "Resolved to plan " + plan);
+ if (LOGD) Log.d(TAG, "Resolved to plan " + plan);
}
return plan;
}
@@ -876,8 +876,6 @@
private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
assertSystemReady();
- // NOTE: if callers want to get non-augmented data, they should go
- // through the public API
return internalGetSummaryForNetwork(template,
NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN, start, end,
NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotalBytes();
@@ -936,7 +934,7 @@
@Override
public String[] getMobileIfaces() {
// TODO (b/192758557): Remove debug log.
- if (ArrayUtils.contains(mMobileIfaces, null)) {
+ if (CollectionUtils.contains(mMobileIfaces, null)) {
throw new NullPointerException(
"null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
}
@@ -985,7 +983,7 @@
@NonNull NetworkStateSnapshot[] networkStates,
@Nullable String activeIface,
@NonNull UnderlyingNetworkInfo[] underlyingNetworkInfos) {
- checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
final long token = Binder.clearCallingIdentity();
try {
@@ -1014,9 +1012,10 @@
private void advisePersistThreshold(long thresholdBytes) {
// clamp threshold into safe range
- mPersistThreshold = MathUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
+ mPersistThreshold = NetworkStatsUtils.constrain(thresholdBytes,
+ 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
if (LOGV) {
- Slog.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to "
+ Log.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to "
+ mPersistThreshold);
}
@@ -1200,13 +1199,13 @@
// On background handler thread, and USER_REMOVED is protected
// broadcast.
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userId == -1) return;
+ final UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (userHandle == null) return;
synchronized (mStatsLock) {
mWakeLock.acquire();
try {
- removeUserLocked(userId);
+ removeUserLocked(userHandle);
} finally {
mWakeLock.release();
}
@@ -1231,7 +1230,7 @@
@Override
public void limitReached(String limitName, String iface) {
// only someone like NMS should be calling us
- NetworkStack.checkNetworkStackPermission(mContext);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
// kick off background poll to collect network stats unless there is already
@@ -1279,7 +1278,7 @@
private void handleNotifyNetworkStatusLocked(@NonNull Network[] defaultNetworks,
@NonNull NetworkStateSnapshot[] snapshots) {
if (!mSystemReady) return;
- if (LOGV) Slog.v(TAG, "handleNotifyNetworkStatusLocked()");
+ if (LOGV) Log.v(TAG, "handleNotifyNetworkStatusLocked()");
// take one last stats snapshot before updating iface mapping. this
// isn't perfect, since the kernel may already be counting traffic from
@@ -1303,7 +1302,8 @@
final int displayTransport =
getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport);
- final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, snapshot.getNetwork());
+ final boolean isDefault = CollectionUtils.contains(
+ mDefaultNetworks, snapshot.getNetwork());
final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED
: getSubTypeForStateSnapshot(snapshot);
final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
@@ -1386,7 +1386,7 @@
mMobileIfaces = mobileIfaces.toArray(new String[0]);
// TODO (b/192758557): Remove debug log.
- if (ArrayUtils.contains(mMobileIfaces, null)) {
+ if (CollectionUtils.contains(mMobileIfaces, null)) {
throw new NullPointerException(
"null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
}
@@ -1401,7 +1401,7 @@
if (spec instanceof TelephonyNetworkSpecifier) {
return ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
} else {
- Slog.wtf(TAG, "getSubIdForState invalid NetworkSpecifier");
+ Log.wtf(TAG, "getSubIdForState invalid NetworkSpecifier");
return INVALID_SUBSCRIPTION_ID;
}
}
@@ -1485,7 +1485,7 @@
try {
recordSnapshotLocked(currentTime);
} catch (IllegalStateException e) {
- Slog.w(TAG, "problem reading network stats: " + e);
+ Log.w(TAG, "problem reading network stats: " + e);
} catch (RemoteException e) {
// ignored; service lives in system_server
}
@@ -1510,7 +1510,7 @@
@GuardedBy("mStatsLock")
private void performPollLocked(int flags) {
if (!mSystemReady) return;
- if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
+ if (LOGV) Log.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
Trace.traceBegin(TRACE_TAG_NETWORK, "performPollLocked");
final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
@@ -1632,7 +1632,7 @@
*/
@GuardedBy("mStatsLock")
private void removeUidsLocked(int... uids) {
- if (LOGV) Slog.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
+ if (LOGV) Log.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
// Perform one last poll before removing
performPollLocked(FLAG_PERSIST_ALL);
@@ -1650,20 +1650,20 @@
* Clean up {@link #mUidRecorder} after user is removed.
*/
@GuardedBy("mStatsLock")
- private void removeUserLocked(int userId) {
- if (LOGV) Slog.v(TAG, "removeUserLocked() for userId=" + userId);
+ private void removeUserLocked(@NonNull UserHandle userHandle) {
+ if (LOGV) Log.v(TAG, "removeUserLocked() for UserHandle=" + userHandle);
// Build list of UIDs that we should clean up
- int[] uids = new int[0];
+ final ArrayList<Integer> uids = new ArrayList<>();
final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
PackageManager.MATCH_ANY_USER
| PackageManager.MATCH_DISABLED_COMPONENTS);
for (ApplicationInfo app : apps) {
- final int uid = UserHandle.getUid(userId, app.uid);
- uids = ArrayUtils.appendInt(uids, uid);
+ final int uid = userHandle.getUid(app.uid);
+ uids.add(uid);
}
- removeUidsLocked(uids);
+ removeUidsLocked(CollectionUtils.toIntArray(uids));
}
private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal {
@@ -1706,7 +1706,7 @@
public void setStatsProviderWarningAndLimitAsync(
@NonNull String iface, long warning, long limit) {
if (LOGV) {
- Slog.v(TAG, "setStatsProviderWarningAndLimitAsync("
+ Log.v(TAG, "setStatsProviderWarningAndLimitAsync("
+ iface + "," + warning + "," + limit + ")");
}
invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
@@ -1716,7 +1716,7 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
+ if (!PermissionUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
long duration = DateUtils.DAY_IN_MILLIS;
final HashSet<String> argSet = new HashSet<String>();
@@ -1777,15 +1777,15 @@
pw.println("Configs:");
pw.increaseIndent();
- pw.printPair(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled());
+ pw.print(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled());
pw.println();
pw.decreaseIndent();
pw.println("Active interfaces:");
pw.increaseIndent();
for (int i = 0; i < mActiveIfaces.size(); i++) {
- pw.printPair("iface", mActiveIfaces.keyAt(i));
- pw.printPair("ident", mActiveIfaces.valueAt(i));
+ pw.print("iface", mActiveIfaces.keyAt(i));
+ pw.print("ident", mActiveIfaces.valueAt(i));
pw.println();
}
pw.decreaseIndent();
@@ -1793,8 +1793,8 @@
pw.println("Active UID interfaces:");
pw.increaseIndent();
for (int i = 0; i < mActiveUidIfaces.size(); i++) {
- pw.printPair("iface", mActiveUidIfaces.keyAt(i));
- pw.printPair("ident", mActiveUidIfaces.valueAt(i));
+ pw.print("iface", mActiveUidIfaces.keyAt(i));
+ pw.print("ident", mActiveUidIfaces.valueAt(i));
pw.println();
}
pw.decreaseIndent();
@@ -1867,7 +1867,7 @@
@GuardedBy("mStatsLock")
private void dumpProtoLocked(FileDescriptor fd) {
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ final ProtoOutputStream proto = new ProtoOutputStream(new FileOutputStream(fd));
// TODO Right now it writes all history. Should it limit to the "since-boot" log?
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
index 5646c75..93d0ae7 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -33,7 +33,7 @@
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.CollectionUtils;
+import com.android.net.module.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@@ -99,18 +99,19 @@
// prevent binder call to telephony when querying RAT. Keep listener registration with empty
// IMSI is meaningless since the RAT type changed is ambiguous for multi-SIM if reported
// with empty IMSI. So filter the subs w/o a valid IMSI to prevent such registration.
- final List<Pair<Integer, String>> filteredNewSubs =
- CollectionUtils.mapNotNull(newSubs, subId -> {
- final String subscriberId = mTeleManager.getSubscriberId(subId);
- return TextUtils.isEmpty(subscriberId) ? null : new Pair(subId, subscriberId);
- });
+ final List<Pair<Integer, String>> filteredNewSubs = new ArrayList<>();
+ for (final int subId : newSubs) {
+ final String subscriberId = mTeleManager.getSubscriberId(subId);
+ if (!TextUtils.isEmpty(subscriberId)) {
+ filteredNewSubs.add(new Pair(subId, subscriberId));
+ }
+ }
for (final Pair<Integer, String> sub : filteredNewSubs) {
// Fully match listener with subId and IMSI, since in some rare cases, IMSI might be
// suddenly change regardless of subId, such as switch IMSI feature in modem side.
// If that happens, register new listener with new IMSI and remove old one later.
- if (CollectionUtils.find(mRatListeners,
- it -> it.equalsKey(sub.first, sub.second)) != null) {
+ if (CollectionUtils.any(mRatListeners, it -> it.equalsKey(sub.first, sub.second))) {
continue;
}
@@ -126,8 +127,8 @@
for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
// If there is no subId and IMSI matched the listener, removes it.
- if (CollectionUtils.find(filteredNewSubs,
- it -> listener.equalsKey(it.first, it.second)) == null) {
+ if (!CollectionUtils.any(filteredNewSubs,
+ it -> listener.equalsKey(it.first, it.second))) {
handleRemoveRatTypeListener(listener);
}
}
@@ -148,9 +149,10 @@
* @return collapsed RatType for the given subscriberId
*/
public int getRatTypeForSubscriberId(@NonNull String subscriberId) {
- final RatTypeListener match = CollectionUtils.find(mRatListeners,
+ final int index = CollectionUtils.indexOf(mRatListeners,
it -> TextUtils.equals(subscriberId, it.mSubscriberId));
- return match != null ? match.mLastCollapsedRatType : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ return index != -1 ? mRatListeners.get(index).mLastCollapsedRatType
+ : TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
/**
diff --git a/packages/CtsShim/OWNERS b/packages/CtsShim/OWNERS
index 9419771..eb631bc 100644
--- a/packages/CtsShim/OWNERS
+++ b/packages/CtsShim/OWNERS
@@ -1,3 +1,2 @@
ioffe@google.com
-toddke@google.com
patb@google.com
\ No newline at end of file
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 48cdf16..197b7b2 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -31,7 +31,7 @@
android:directBootAware="true">
<receiver android:name=".TemporaryFileManager"
- android:exported="true">
+ android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
@@ -76,7 +76,7 @@
<receiver android:name=".InstallEventReceiver"
android:permission="android.permission.INSTALL_PACKAGES"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" />
</intent-filter>
@@ -106,14 +106,14 @@
<receiver android:name=".UninstallEventReceiver"
android:permission="android.permission.INSTALL_PACKAGES"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="com.android.packageinstaller.ACTION_UNINSTALL_COMMIT" />
</intent-filter>
</receiver>
<receiver android:name=".PackageInstalledReceiver"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="android.intent.action.PACKAGE_ADDED" />
<data android:scheme="package" />
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 5d7b9bb..cef9014 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -19,6 +19,9 @@
},
{
"name": "CtsPackageUninstallTestCases"
+ },
+ {
+ "name": "PackageInstallerTests"
}
]
-}
\ No newline at end of file
+}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
index 5f107d6..34e7e3d 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
@@ -84,8 +84,7 @@
*/
public MDNSFilterPlugin(@NonNull Context context, @NonNull String name,
@NonNull CharSequence packageName, @NonNull List<String> mDNSNames) {
- mName = context.getResources().getIdentifier(name, null,
- "com.android.printservice.recommendation");
+ mName = context.getResources().getIdentifier(name, null, context.getPackageName());
mPackageName = packageName;
mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPES,
new VendorNameFilter(new HashSet<>(mDNSNames)));
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
index e74ac44..fede44f 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
@@ -33,18 +33,18 @@
<style name="Banner.Title.SettingsLib"
parent="@android:style/TextAppearance.Material.Subhead">
<item name="android:textSize">20sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="Banner.Subtitle.SettingsLib"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">14sp</item>
</style>
<style name="Banner.Summary.SettingsLib"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">14sp</item>
</style>
@@ -58,4 +58,4 @@
parent="android:Widget.DeviceDefault.Button.Borderless.Colored">
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
index df47c64..4c6ed58 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
@@ -17,14 +17,13 @@
<resources>
<style name="Banner.Text.Title"
- parent="@android:style/TextAppearance.Material.Subhead">
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textSize">16sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="Banner.Text.Summary"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">14sp</item>
</style>
diff --git a/packages/SettingsLib/BarChartPreference/res/values/styles.xml b/packages/SettingsLib/BarChartPreference/res/values/styles.xml
index 92514ad..1c44207 100644
--- a/packages/SettingsLib/BarChartPreference/res/values/styles.xml
+++ b/packages/SettingsLib/BarChartPreference/res/values/styles.xml
@@ -87,9 +87,9 @@
</style>
<style name="BarChart.Text"
- parent="@android:style/TextAppearance.Material.Subhead">
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">16sp</item>
</style>
<style name="BarChart.Text.HeaderTitle">
@@ -101,7 +101,7 @@
</style>
<style name="BarChart.Text.Summary"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">12sp</item>
</style>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 2f911c4..238e65e 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -19,6 +19,7 @@
"com.google.android.material_material",
"SettingsLibSettingsTransition",
"SettingsLibUtils",
+ "SettingsLibSettingsTheme",
],
sdk_version: "system_current",
min_sdk_version: "29",
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
index 5950656..907863e 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
@@ -16,7 +16,6 @@
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content_parent"
android:layout_width="match_parent"
@@ -40,7 +39,7 @@
android:clipToPadding="false"
app:forceApplySystemWindowInsetTop="true"
app:extraMultilineHeightEnabled="true"
- app:contentScrim="?androidprv:attr/colorSurfaceHeader"
+ app:contentScrim="@color/settingslib_colorSurfaceHeader"
app:maxLines="3"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:scrimAnimationDuration="50"
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
similarity index 83%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
index 878275a0..c20beaf 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
@@ -18,7 +18,7 @@
<style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
<item name="elevationOverlayEnabled">true</item>
<item name="elevationOverlayColor">?attr/colorPrimary</item>
- <item name="colorPrimary">@*android:color/primary_dark_device_default_settings</item>
- <item name="colorAccent">@*android:color/accent_device_default_dark</item>
+ <item name="colorPrimary">@color/settingslib_primary_dark_device_default_settings</item>
+ <item name="colorAccent">@color/settingslib_accent_device_default_dark</item>
</style>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml
similarity index 77%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml
index 63d397c..d0b6c4d 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml
@@ -16,11 +16,13 @@
-->
<resources>
<style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
<item name="android:textSize">20dp</item>
+ <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item>
</style>
<style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
<item name="android:textSize">36dp</item>
+ <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item>
</style>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
similarity index 82%
copy from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
copy to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
index 878275a0..9ecc297 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
@@ -18,7 +18,7 @@
<style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
<item name="elevationOverlayEnabled">true</item>
<item name="elevationOverlayColor">?attr/colorPrimary</item>
- <item name="colorPrimary">@*android:color/primary_dark_device_default_settings</item>
- <item name="colorAccent">@*android:color/accent_device_default_dark</item>
+ <item name="colorPrimary">@color/settingslib_primary_device_default_settings_light</item>
+ <item name="colorAccent">@color/settingslib_accent_device_default_light</item>
</style>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml
deleted file mode 100644
index 2e7a6a9..0000000
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
--->
-<resources>
- <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
- <item name="elevationOverlayEnabled">true</item>
- <item name="elevationOverlayColor">?attr/colorPrimary</item>
- <item name="colorPrimary">@*android:color/primary_device_default_settings_light</item>
- <item name="colorAccent">@*android:color/accent_device_default_light</item>
- </style>
-</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/FooterPreference/res/values/styles.xml b/packages/SettingsLib/FooterPreference/res/values/styles.xml
index 08dd359..5a3bada 100644
--- a/packages/SettingsLib/FooterPreference/res/values/styles.xml
+++ b/packages/SettingsLib/FooterPreference/res/values/styles.xml
@@ -17,9 +17,8 @@
<resources>
<style name="TextAppearance.Footer.Title.SettingsLib"
- parent="@android:style/TextAppearance.DeviceDefault.Medium">
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textSize">14sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/LayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
index 4a99e84..2ffe6d9 100644
--- a/packages/SettingsLib/LayoutPreference/res/values/styles.xml
+++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
@@ -24,14 +24,13 @@
</style>
<style name="TextAppearance.EntityHeaderTitle"
- parent="@android:style/TextAppearance.Material.Subhead">
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.EntityHeaderSummary"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textAlignment">viewStart</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:singleLine">true</item>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml
rename to packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml
rename to packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml
rename to packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index 6e5911c..30748e6 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -50,7 +50,7 @@
android:tint="?android:attr/colorAccent"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end"
- android:src="@*android:drawable/ic_info"
+ android:src="@android:drawable/ic_info"
android:visibility="gone" />
<Switch
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 306145a..d0c2d0b 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -28,7 +28,7 @@
android:layout_gravity="center_vertical"
android:maxLines="2"
android:ellipsize="end"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimaryInverse"
android:layout_marginStart="@dimen/settingslib_switchbar_subsettings_margin_start"
@@ -42,7 +42,7 @@
android:theme="@android:style/Theme.Material"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end"
- android:src="@*android:drawable/ic_info"
+ android:src="@android:drawable/ic_info"
android:visibility="gone"/>
<Switch
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
new file mode 100644
index 0000000..2272a37
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
@@ -0,0 +1,37 @@
+<?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.
+ -->
+
+<resources>
+
+ <!-- Size of layout margin -->
+ <dimen name="settingslib_switchbar_margin">16dp</dimen>
+
+ <!-- Size of layout margin left -->
+ <dimen name="settingslib_switchbar_padding_left">24dp</dimen>
+
+ <!-- Size of layout margin right -->
+ <dimen name="settingslib_switchbar_padding_right">16dp</dimen>
+
+ <!-- Minimum width of switch -->
+ <dimen name="settingslib_min_switch_width">52dp</dimen>
+
+ <!-- Minimum width of switch bar -->
+ <dimen name="settingslib_min_switch_bar_height">72dp</dimen>
+
+ <!-- Radius of switch bar -->
+ <dimen name="settingslib_switch_bar_radius">28dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
new file mode 100644
index 0000000..a50fc7c
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.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.
+ -->
+
+<resources>
+
+ <style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:textSize">20sp</item>
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+ <item name="android:textColor">@android:color/black</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index 16b8af6..6362882 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2020 The Android Open Source Project
+ 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.
@@ -17,30 +17,12 @@
<resources>
- <!-- Size of layout margin -->
- <dimen name="settingslib_switchbar_margin">16dp</dimen>
-
- <!-- Size of layout margin left -->
- <dimen name="settingslib_switchbar_padding_left">24dp</dimen>
-
- <!-- Size of layout margin right -->
- <dimen name="settingslib_switchbar_padding_right">16dp</dimen>
-
- <!-- Minimum width of switch -->
- <dimen name="settingslib_min_switch_width">52dp</dimen>
-
- <!-- Minimum width of switch bar -->
- <dimen name="settingslib_min_switch_bar_height">72dp</dimen>
-
<!-- Restricted icon size in switch bar -->
- <dimen name="settingslib_restricted_icon_size">@*android:dimen/config_restrictedIconSize</dimen>
+ <dimen name="settingslib_restricted_icon_size">@android:dimen/config_restrictedIconSize</dimen>
<!-- Restricted icon in switch bar -->
<dimen name="settingslib_restricted_icon_margin_end">16dp</dimen>
- <!-- Radius of switch bar -->
- <dimen name="settingslib_switch_bar_radius">28dp</dimen>
-
<!-- Size of title margin -->
<dimen name="settingslib_switch_title_margin">16dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
index 3924e30..870812a 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2020 The Android Open Source Project
+ 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.
@@ -17,13 +17,6 @@
<resources>
- <style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
- <item name="android:textSize">20sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">@android:color/black</item>
- </style>
-
-
<style name="SwitchBar.Switch.Settingslib" parent="@android:style/Widget.Material.CompoundButton.Switch">
<item name="android:trackTint">@color/settingslib_switchbar_switch_track_tint</item>
<item name="android:thumbTint">@color/settingslib_switchbar_switch_thumb_tint</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
new file mode 100644
index 0000000..037b80a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 8c7c7ed..c206903 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -36,4 +36,11 @@
<color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color>
+
+ <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color>
+
+ <!-- copy from accent_primary_variant_dark_device_default-->
+ <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
+
+ <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index 77f1bcd..0401098 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -37,9 +37,32 @@
<!-- Dialog accent color -->
<color name="settingslib_dialog_accent">@android:color/system_accent1_600</color>
<!-- Dialog background color -->
- <color name="settingslib_dialog_background">@*android:color/surface_light</color>
+ <color name="settingslib_dialog_background">@color/settingslib_surface_light</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 -->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color>
+
+ <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color>
+
+ <color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color>
+
+ <color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color>
+
+ <color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color>
+
+ <color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color>
+
+ <color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color>
+
+ <!-- copy from accent_primary_variant_light_device_default-->
+ <color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color>
+
+ <color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color>
+
+ <color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color>
+
+ <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
+
+ <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
index ddcc83e..1c33f1a 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
@@ -19,4 +19,5 @@
<dimen name="app_preference_padding_start">20dp</dimen>
<dimen name="app_icon_min_width">52dp</dimen>
<dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen>
+ <dimen name="settingslib_dialogCornerRadius">28dp</dimen>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml
new file mode 100644
index 0000000..6d072a9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of a font family to use for headlines in SettingsLib. -->
+ <string name="settingslib_config_headlineFontFamily" translatable="false">
+ @*android:string/config_headlineFontFamily
+ </string>
+
+ <!-- Name of a font family to use for headlines-medium in SettingsLib. -->
+ <string name="settingslib_config_headlineFontFamilyMedium" translatable="false">
+ @*android:string/config_headlineFontFamilyMedium
+ </string>
+
+ <!-- Name of a font family to use for body in SettingsLib. -->
+ <string name="settingslib_config_bodyFontFamily" translatable="false">
+ @*android:string/config_bodyFontFamily
+ </string>
+
+ <!-- Name of a font family to use for body-medium in SettingsLib. -->
+ <string name="settingslib_config_bodyFontFamilyMedium" translatable="false">
+ @*android:string/config_bodyFontFamilyMedium
+ </string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index 46f1e03..5800636 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -16,12 +16,16 @@
-->
<resources>
<style name="TextAppearance.PreferenceTitle.SettingsLib"
- parent="@*android:style/TextAppearance.DeviceDefault.ListItem">
+ parent="@android:style/TextAppearance.Material.Subhead">
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.CategoryTitle.SettingsLib"
- parent="@*android:style/TextAppearance.DeviceDefault.Body2" />
+ parent="@android:style/TextAppearance.DeviceDefault.Medium">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">14sp</item>
+ </style>
<style name="Switch.SettingsLib" parent="@android:style/Widget.Material.CompoundButton.Switch">
<item name="android:switchMinWidth">52dp</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 8034710..6bf288b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -50,6 +50,6 @@
<item name="android:clipToPadding">true</item>
<item name="android:clipChildren">true</item>
- <item name="dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+ <item name="dialogCornerRadius">@dimen/settingslib_dialogCornerRadius</item>
</style>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
index 25f9514..18af1f9 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
@@ -20,4 +20,5 @@
<dimen name="app_preference_padding_start">?android:attr/listPreferredItemPaddingStart</dimen>
<dimen name="app_icon_min_width">56dp</dimen>
<dimen name="two_target_min_width">72dp</dimen>
+ <dimen name="settingslib_dialogCornerRadius">8dp</dimen>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
index 6f25177..2d881d1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
@@ -35,7 +35,7 @@
<!-- TODO(b/189308264): fix the crash in Android R if set the attributes:
<item name="colorAccent">@*android:color/accent_device_default_light</item>
<item name="android:colorBackground">@color/settingslib_dialog_background</item>
- <item name="dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+ <item name="dialogCornerRadius">@dimen/settingslib_dialogCornerRadius</item>
-->
<item name="android:windowSoftInputMode">adjustResize</item>
<item name="android:clipToPadding">true</item>
diff --git a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
index 65869b5..b6ca41f 100644
--- a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
@@ -16,8 +16,7 @@
-->
<resources>
<style name="TextAppearance.TopIntroText"
- parent="@*android:style/TextAppearance.DeviceDefault">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index b429fe6..3debd9a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -87,7 +87,7 @@
}
// Check the member devices for the coordinated set if it exists
final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
- if (memberDevices != null) {
+ if (!memberDevices.isEmpty()) {
for (CachedBluetoothDevice memberDevice : memberDevices) {
if (memberDevice.getDevice().equals(device)) {
return memberDevice;
@@ -141,7 +141,7 @@
final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
// TODO: check the CSIP group size instead of the real member device set size, and adjust
// the size restriction.
- if (memberDevices.size() == 1) {
+ if (!memberDevices.isEmpty()) {
for (CachedBluetoothDevice memberDevice : memberDevices) {
if (memberDevice.isConnected()) {
return memberDevice.getConnectionSummary();
@@ -166,7 +166,7 @@
if (!cachedDevice.getDevice().equals(device)) {
// Check the member devices of the coordinated set if it exists
Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
- if (memberDevices != null) {
+ if (!memberDevices.isEmpty()) {
for (CachedBluetoothDevice memberDevice : memberDevices) {
if (memberDevice.getDevice().equals(device)) {
return true;
@@ -230,9 +230,10 @@
private void clearNonBondedSubDevices() {
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
- final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
- if (memberDevices != null) {
- for (CachedBluetoothDevice memberDevice : memberDevices) {
+ Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+ if (!memberDevices.isEmpty()) {
+ for (Object it : memberDevices.toArray()) {
+ CachedBluetoothDevice memberDevice = (CachedBluetoothDevice) it;
// Member device exists and it is not bonded
if (memberDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
cachedDevice.removeMemberDevice(memberDevice);
@@ -257,7 +258,7 @@
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
cachedDevice.setJustDiscovered(false);
final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
- if (memberDevices != null) {
+ if (!memberDevices.isEmpty()) {
for (CachedBluetoothDevice memberDevice : memberDevices) {
memberDevice.setJustDiscovered(false);
}
@@ -277,7 +278,7 @@
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
- if (memberDevices != null) {
+ if (!memberDevices.isEmpty()) {
for (CachedBluetoothDevice memberDevice : memberDevices) {
if (memberDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
cachedDevice.removeMemberDevice(memberDevice);
@@ -315,7 +316,7 @@
public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
- if (memberDevices != null) {
+ if (!memberDevices.isEmpty()) {
// Main device is unpaired, to unpair the member device
for (CachedBluetoothDevice memberDevice : memberDevices) {
memberDevice.unpair();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 1d29966..cc56a21 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -68,7 +68,7 @@
}
for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
- if (entry.getValue().equals(BluetoothUuid.BASE_UUID)) {
+ if (entry.getValue().equals(BluetoothUuid.CAP)) {
return entry.getKey();
}
}
@@ -193,7 +193,7 @@
return true;
}
final Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice();
- if (memberSet == null) {
+ if (memberSet.isEmpty()) {
break;
}
@@ -225,7 +225,7 @@
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
if (isValidGroupId(cachedDevice.getGroupId())) {
Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice();
- if (memberSet == null) {
+ if (memberSet.isEmpty()) {
continue;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 3347920..5e2f310 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -338,7 +338,7 @@
.getGroupUuidMapByDevice(cachedDevice.getDevice());
if (groupIdMap != null) {
for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
- if (entry.getValue().equals(BluetoothUuid.BASE_UUID)) {
+ if (entry.getValue().equals(BluetoothUuid.CAP)) {
cachedDevice.setGroupId(entry.getKey());
break;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index 18c38c5..011ca0b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -32,7 +32,6 @@
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -172,7 +171,7 @@
return bucket.getRxBytes() + bucket.getTxBytes();
}
Log.w(TAG, "Failed to get data usage, no entry data");
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.w(TAG, "Failed to get data usage, remote call failed");
}
return -1L;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java
index 787dc55..42e7100 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java
@@ -18,7 +18,6 @@
import android.app.usage.NetworkStats;
import android.content.Context;
-import android.os.RemoteException;
import android.util.Log;
import java.util.ArrayList;
@@ -54,7 +53,7 @@
.setTotalUsage(total);
mData.add(builder.build());
}
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.e(TAG, "Exception querying network detail.", e);
}
}
@@ -85,7 +84,7 @@
if (bucket != null) {
usage = bucket.getRxBytes() + bucket.getTxBytes();
}
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.e(TAG, "Exception querying network detail.", e);
}
data.add(new NetworkCycleData.Builder()
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
index ed093629..54d5c3d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
@@ -20,7 +20,6 @@
import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.net.NetworkTemplate;
-import android.os.RemoteException;
import android.util.Log;
import androidx.loader.content.AsyncTaskLoader;
@@ -55,7 +54,7 @@
public NetworkStats loadInBackground() {
try {
return mNetworkStatsManager.querySummary(mNetworkTemplate, mStart, mEnd);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.e(TAG, "Exception querying network detail.", e);
return null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 72fa25f..bf0dc7b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -157,7 +157,6 @@
private Network mDefaultNetwork = null;
private NetworkCapabilities mDefaultNetworkCapabilities = null;
private final Runnable mCallback;
- private final boolean mSupportMergedUi;
private WifiInfo mWifiInfo;
public boolean enabled;
@@ -181,7 +180,6 @@
mNetworkScoreManager = networkScoreManager;
mConnectivityManager = connectivityManager;
mCallback = callback;
- mSupportMergedUi = false;
}
public void setListening(boolean listening) {
@@ -223,10 +221,8 @@
} else {
ssid = getValidSsid(mWifiInfo);
}
- if (mSupportMergedUi) {
- isCarrierMerged = mWifiInfo.isCarrierMerged();
- subId = mWifiInfo.getSubscriptionId();
- }
+ isCarrierMerged = mWifiInfo.isCarrierMerged();
+ subId = mWifiInfo.getSubscriptionId();
updateRssi(mWifiInfo.getRssi());
maybeRequestNetworkScore();
}
@@ -255,10 +251,8 @@
} else {
ssid = getValidSsid(mWifiInfo);
}
- if (mSupportMergedUi) {
- isCarrierMerged = mWifiInfo.isCarrierMerged();
- subId = mWifiInfo.getSubscriptionId();
- }
+ isCarrierMerged = mWifiInfo.isCarrierMerged();
+ subId = mWifiInfo.getSubscriptionId();
updateRssi(mWifiInfo.getRssi());
maybeRequestNetworkScore();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 6100615..56454e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -20,10 +20,13 @@
import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiInfo;
+import android.os.Bundle;
import android.os.SystemClock;
import androidx.annotation.VisibleForTesting;
@@ -36,6 +39,23 @@
private static final int INVALID_RSSI = -127;
+ /**
+ * The intent action shows network details settings to allow configuration of Wi-Fi.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: The calling package should put the chosen
+ * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
+ * the {@link #KEY_CHOSEN_WIFIENTRY_KEY}.
+ * <p>
+ * Output: Nothing.
+ */
+ public static final String ACTION_WIFI_DETAILS_SETTINGS =
+ "android.settings.WIFI_DETAILS_SETTINGS";
+ public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
+ public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
+
static final int[] WIFI_PIE = {
com.android.internal.R.drawable.ic_wifi_signal_0,
com.android.internal.R.drawable.ic_wifi_signal_1,
@@ -275,7 +295,42 @@
return noInternet ? NO_INTERNET_WIFI_PIE[level] : WIFI_PIE[level];
}
+ /**
+ * Wrapper the {@link #getInternetIconResource} for testing compatibility.
+ */
+ public static class InternetIconInjector {
+
+ protected final Context mContext;
+
+ public InternetIconInjector(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Returns the Internet icon for a given RSSI level.
+ *
+ * @param noInternet True if a connected Wi-Fi network cannot access the Internet
+ * @param level The number of bars to show (0-4)
+ */
+ public Drawable getIcon(boolean noInternet, int level) {
+ return mContext.getDrawable(WifiUtils.getInternetIconResource(level, noInternet));
+ }
+ }
+
public static boolean isMeteredOverridden(WifiConfiguration config) {
return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE;
}
+
+ /**
+ * Returns the Intent for Wi-Fi network details settings.
+ *
+ * @param key The Wi-Fi entry key
+ */
+ public static Intent getWifiDetailsSettingsIntent(String key) {
+ final Intent intent = new Intent(ACTION_WIFI_DETAILS_SETTINGS);
+ final Bundle bundle = new Bundle();
+ bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
+ return intent;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 2c4f57f..d53a3e8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -26,7 +26,9 @@
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothUuid;
import android.content.Context;
+import android.os.ParcelUuid;
import org.junit.Before;
import org.junit.Test;
@@ -37,6 +39,8 @@
import org.robolectric.RuntimeEnvironment;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
@RunWith(RobolectricTestRunner.class)
public class CachedBluetoothDeviceManagerTest {
@@ -51,6 +55,10 @@
private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
private final static long HISYNCID1 = 10;
private final static long HISYNCID2 = 11;
+ private final static Map<Integer, ParcelUuid> CAP_GROUP1 =
+ Map.of(1, BluetoothUuid.CAP);
+ private final static Map<Integer, ParcelUuid> CAP_GROUP2 =
+ Map.of(2, BluetoothUuid.CAP);
private final BluetoothClass DEVICE_CLASS_1 =
new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
private final BluetoothClass DEVICE_CLASS_2 =
@@ -70,6 +78,8 @@
@Mock
private HearingAidProfile mHearingAidProfile;
@Mock
+ private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
+ @Mock
private BluetoothDevice mDevice1;
@Mock
private BluetoothDevice mDevice2;
@@ -105,8 +115,12 @@
when(mA2dpProfile.isProfileReady()).thenReturn(true);
when(mPanProfile.isProfileReady()).thenReturn(true);
when(mHearingAidProfile.isProfileReady()).thenReturn(true);
+ when(mCsipSetCoordinatorProfile.isProfileReady())
+ .thenReturn(true);
doAnswer((invocation) -> mHearingAidProfile).
when(mLocalProfileManager).getHearingAidProfile();
+ doAnswer((invocation) -> mCsipSetCoordinatorProfile)
+ .when(mLocalProfileManager).getCsipSetCoordinatorProfile();
mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
@@ -298,7 +312,7 @@
* Test to verify OnDeviceUnpaired() for main hearing Aid device unpair.
*/
@Test
- public void onDeviceUnpaired_unpairMainDevice() {
+ public void onDeviceUnpaired_unpairHearingAidMainDevice() {
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
@@ -398,4 +412,153 @@
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
assertThat(mCachedDeviceManager.onDeviceDisappeared(cachedDevice1)).isTrue();
}
+
+ /**
+ * Test to verify getMemberDevice(), new device has the same group id.
+ */
+ @Test
+ public void addDevice_sameGroupId_validMemberDevice() {
+ doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice1);
+ doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice2);
+ doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice3);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+ CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
+
+ assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
+ assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
+ }
+
+ /**
+ * Test to verify getMemberDevice(), new device has the different group id.
+ */
+ @Test
+ public void addDevice_differentGroupId_validMemberDevice() {
+ doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice1);
+ doAnswer((invocation) -> CAP_GROUP2).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice2);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+ assertThat(cachedDevice1.getMemberDevice()).isEmpty();
+ }
+
+ /**
+ * Test to verify addDevice(), new device has the same group id.
+ */
+ @Test
+ public void addDevice_sameGroupId_validCachedDevices_mainDevicesAdded_memberDevicesNotAdded() {
+ doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice1);
+ doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice2);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+ Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+ assertThat(devices).contains(cachedDevice1);
+ assertThat(devices).doesNotContain(cachedDevice2);
+ }
+
+ /**
+ * Test to verify addDevice(), new device has the different group id.
+ */
+ @Test
+ public void addDevice_differentGroupId_validCachedDevices_bothAdded() {
+ doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice1);
+ doAnswer((invocation) -> CAP_GROUP2).when(mCsipSetCoordinatorProfile)
+ .getGroupUuidMapByDevice(mDevice2);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+ Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+ assertThat(devices).contains(cachedDevice1);
+ assertThat(devices).contains(cachedDevice2);
+ }
+
+ /**
+ * Test to verify clearNonBondedDevices() for csip set member device.
+ */
+ @Test
+ public void clearNonBondedDevices_nonBondedMemberDevice() {
+ when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+ when(mDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+ CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
+ cachedDevice1.setMemberDevice(cachedDevice2);
+ cachedDevice1.setMemberDevice(cachedDevice3);
+
+ assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
+ assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
+ mCachedDeviceManager.clearNonBondedDevices();
+
+ assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isFalse();
+ assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
+ }
+
+ /**
+ * Test to verify OnDeviceUnpaired() for csip device unpair.
+ */
+ @Test
+ public void onDeviceUnpaired_unpairCsipMainDevice() {
+ when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+ cachedDevice1.setGroupId(1);
+ cachedDevice2.setGroupId(1);
+ cachedDevice1.setMemberDevice(cachedDevice2);
+
+ // Call onDeviceUnpaired for the one in mCachedDevices.
+ mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
+ verify(mDevice2).removeBond();
+ }
+
+ /**
+ * Test to verify OnDeviceUnpaired() for csip device unpair.
+ */
+ @Test
+ public void onDeviceUnpaired_unpairCsipSubDevice() {
+ when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+ cachedDevice1.setGroupId(1);
+ cachedDevice2.setGroupId(1);
+ cachedDevice1.setMemberDevice(cachedDevice2);
+
+ // Call onDeviceUnpaired for the one in mCachedDevices.
+ mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
+ verify(mDevice1).removeBond();
+ }
+
+ /**
+ * Test to verify isSubDevice_validSubDevice().
+ */
+ @Test
+ public void isSubDevice_validMemberDevice() {
+ doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
+ mCachedDeviceManager.addDevice(mDevice1);
+
+ // Both device are not sub device in default value.
+ assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
+ assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isFalse();
+
+ // Add Device-2 as device with Device-1 with the same group id, and add Device-3 with
+ // the different group id.
+ doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice2);
+ doReturn(CAP_GROUP2).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice3);
+ mCachedDeviceManager.addDevice(mDevice2);
+ mCachedDeviceManager.addDevice(mDevice3);
+
+ // Verify Device-2 is sub device, but Device-1, and Device-3 is not.
+ assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
+ assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
+ assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 89960cb..7c2b904 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -20,9 +20,12 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
import android.net.NetworkKey;
import android.net.RssiCurve;
import android.net.ScoredNetwork;
@@ -36,6 +39,8 @@
import android.text.format.DateUtils;
import android.util.ArraySet;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.settingslib.R;
import org.junit.Before;
@@ -44,7 +49,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Set;
@@ -69,7 +73,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = spy(ApplicationProvider.getApplicationContext());
}
@Test
@@ -148,6 +152,32 @@
assertThat(WifiUtils.isMeteredOverridden(mWifiConfig)).isTrue();
}
+ @Test
+ public void getWifiDetailsSettingsIntent_returnsCorrectValues() {
+ final String key = "test_key";
+
+ final Intent intent = WifiUtils.getWifiDetailsSettingsIntent(key);
+
+ assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DETAILS_SETTINGS);
+ final Bundle bundle = intent.getBundleExtra(WifiUtils.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(bundle.getString(WifiUtils.KEY_CHOSEN_WIFIENTRY_KEY)).isEqualTo(key);
+ }
+
+ @Test
+ public void testInternetIconInjector_getIcon_returnsCorrectValues() {
+ WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
+
+ for (int level = 0; level <= 4; level++) {
+ iconInjector.getIcon(false /* noInternet */, level);
+ verify(mContext).getDrawable(
+ WifiUtils.getInternetIconResource(level, false /* noInternet */));
+
+ iconInjector.getIcon(true /* noInternet */, level);
+ verify(mContext).getDrawable(
+ WifiUtils.getInternetIconResource(level, true /* noInternet */));
+ }
+ }
+
private static ArrayList<ScanResult> buildScanResultCache() {
ArrayList<ScanResult> scanResults = new ArrayList<>();
for (int i = 0; i < 5; i++) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index eb81961..a46d28b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -76,5 +76,6 @@
Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
Settings.Global.DEVICE_CONFIG_SYNC_DISABLED,
Settings.Global.POWER_BUTTON_LONG_PRESS,
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 6022608..96f127b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -101,6 +101,7 @@
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
Settings.Secure.QS_TILES,
+ Settings.Secure.QS_AUTO_ADDED_TILES,
Settings.Secure.CONTROLS_ENABLED,
Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT,
Settings.Secure.DOZE_ENABLED,
@@ -118,7 +119,6 @@
Settings.Secure.VR_DISPLAY_MODE,
Settings.Secure.NOTIFICATION_BADGING,
Settings.Secure.NOTIFICATION_DISMISS_RTL,
- Settings.Secure.QS_AUTO_ADDED_TILES,
Settings.Secure.SCREENSAVER_ENABLED,
Settings.Secure.SCREENSAVER_COMPONENTS,
Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 5220a04..84c5feb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -20,6 +20,7 @@
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR;
import static android.view.Display.HdrCapabilities.HDR_TYPES;
@@ -140,6 +141,7 @@
/* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
VALIDATORS.put(Global.DISABLE_WINDOW_BLURS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.DEVICE_CONFIG_SYNC_DISABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 268603f..200e902 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1348,7 +1348,6 @@
Settings.Global.CONNECTIVITY_CHANGE_DELAY,
Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED,
Settings.Global.CAPTIVE_PORTAL_SERVER,
- Settings.Global.NSD_ON,
Settings.Global.SET_INSTALL_LOCATION,
Settings.Global.DEFAULT_INSTALL_LOCATION,
Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index c577868..6cfcb51 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -72,8 +72,9 @@
* {@hide}
*/
private static final ArraySet<String> sBroadcastOnRestore;
+ private static final ArraySet<String> sBroadcastOnRestoreSystemUI;
static {
- sBroadcastOnRestore = new ArraySet<String>(4);
+ sBroadcastOnRestore = new ArraySet<String>(9);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
@@ -83,6 +84,9 @@
sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+ sBroadcastOnRestoreSystemUI = new ArraySet<String>(2);
+ sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES);
+ sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES);
}
private interface SettingsLookup {
@@ -133,6 +137,7 @@
// Will we need a post-restore broadcast for this element?
String oldValue = null;
boolean sendBroadcast = false;
+ boolean sendBroadcastSystemUI = false;
final SettingsLookup table;
if (destination.equals(Settings.Secure.CONTENT_URI)) {
@@ -143,10 +148,12 @@
table = sGlobalLookup;
}
- if (sBroadcastOnRestore.contains(name)) {
+ sendBroadcast = sBroadcastOnRestore.contains(name);
+ sendBroadcastSystemUI = sBroadcastOnRestoreSystemUI.contains(name);
+
+ if (sendBroadcast || sendBroadcastSystemUI) {
// TODO: http://b/22388012
oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM);
- sendBroadcast = true;
}
try {
@@ -193,18 +200,28 @@
} catch (Exception e) {
// If we fail to apply the setting, by definition nothing happened
sendBroadcast = false;
+ sendBroadcastSystemUI = false;
} finally {
// If this was an element of interest, send the "we just restored it"
// broadcast with the historical value now that the new value has
// been committed and observers kicked off.
- if (sendBroadcast) {
+ if (sendBroadcast || sendBroadcastSystemUI) {
Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
- .setPackage("android").addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
.putExtra(Intent.EXTRA_SETTING_NAME, name)
.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value)
.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue)
.putExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, restoredFromSdkInt);
- context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+
+ if (sendBroadcast) {
+ intent.setPackage("android");
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+ }
+ if (sendBroadcastSystemUI) {
+ intent.setPackage(
+ context.getString(com.android.internal.R.string.config_systemUi));
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+ }
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 6c6b0e8..63c874a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -763,9 +763,6 @@
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES);
dumpSetting(s, p,
- Settings.Global.ANGLE_ALLOWLIST,
- GlobalSettingsProto.Gpu.ANGLE_ALLOWLIST);
- dumpSetting(s, p,
Settings.Global.ANGLE_EGL_FEATURES,
GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES);
dumpSetting(s, p,
@@ -1118,10 +1115,6 @@
p.end(notificationToken);
dumpSetting(s, p,
- Settings.Global.NSD_ON,
- GlobalSettingsProto.NSD_ON);
-
- dumpSetting(s, p,
Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE,
GlobalSettingsProto.NR_NSA_TRACKING_SCREEN_OFF_MODE);
@@ -1198,6 +1191,9 @@
dumpSetting(s, p,
Settings.Global.POWER_MANAGER_CONSTANTS,
GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
+ GlobalSettingsProto.POWER_BUTTON_LONG_PRESS_DURATION_MS);
final long prepaidSetupToken = p.start(GlobalSettingsProto.PREPAID_SETUP);
dumpSetting(s, p,
@@ -1479,6 +1475,9 @@
Settings.Global.USE_OPEN_WIFI_PACKAGE,
GlobalSettingsProto.USE_OPEN_WIFI_PACKAGE);
dumpSetting(s, p,
+ Settings.Global.UWB_ENABLED,
+ GlobalSettingsProto.UWB_ENABLED);
+ dumpSetting(s, p,
Settings.Global.VT_IMS_ENABLED,
GlobalSettingsProto.VT_IMS_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 773b068..10dafd9 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -394,7 +394,6 @@
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
Settings.Global.NOTIFICATION_FEEDBACK_ENABLED,
Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE,
- Settings.Global.NSD_ON,
Settings.Global.NTP_SERVER,
Settings.Global.NTP_TIMEOUT,
Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE,
@@ -510,7 +509,6 @@
Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE,
Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS,
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
- Settings.Global.ANGLE_ALLOWLIST,
Settings.Global.ANGLE_EGL_FEATURES,
Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
@@ -520,6 +518,7 @@
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
+ Settings.Global.UWB_ENABLED,
Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX,
Settings.Global.GPU_DEBUG_LAYER_APP,
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 87d50f2..618a37e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -165,6 +165,7 @@
<uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
<uses-permission android:name="android.permission.INSTALL_TEST_ONLY_PACKAGE" />
<uses-permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS" />
+ <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
<uses-permission android:name="android.permission.MOVE_PACKAGE" />
<uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" />
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
@@ -236,6 +237,7 @@
<uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
<uses-permission android:name="android.permission.ACTIVITY_EMBEDDING" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.MANAGE_CLOUDSEARCH" />
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
<uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" />
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 177f86b..80bb307 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -6,7 +6,6 @@
svetoslavganov@google.com
hackbod@google.com
yamasani@google.com
-toddke@google.com
patb@google.com
cbrubaker@google.com
omakoto@google.com
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9de1c5e..8dc18c0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -251,6 +251,8 @@
<uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
<!-- For handling silent audio recordings -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+ <!-- For asking AudioManager audio information -->
+ <uses-permission android:name="android.permission.QUERY_AUDIO_STATE"/>
<!-- to read and change hvac values in a car -->
<uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml b/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
new file mode 100644
index 0000000..495fbb8
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="24dp"/>
+ <gradient
+ android:angle="0"
+ android:startColor="#00000000"
+ android:endColor="#ff000000"
+ android:type="linear" />
+</shape>
diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml
index a4d0ff7..1934457 100644
--- a/packages/SystemUI/res-keyguard/values/donottranslate.xml
+++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml
@@ -21,6 +21,9 @@
<!-- Skeleton string format for displaying the date when an alarm is set. -->
<string name="abbrev_wday_month_day_no_year_alarm">EEEMMMd</string>
+ <!-- Skeleton string format for displaying the date shorter. -->
+ <string name="abbrev_month_day_no_year">MMMd</string>
+
<!-- Skeleton string format for displaying the time in 12-hour format. -->
<string name="clock_12hr_format">hm</string>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 72b027a..098b7e8 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -142,4 +142,8 @@
<item name="android:shadowColor">@color/keyguard_shadow_color</item>
<item name="android:shadowRadius">?attr/shadowRadius</item>
</style>
+
+ <style name="TextAppearance.Keyguard.BottomArea.Button">
+ <item name="android:shadowRadius">0</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml
new file mode 100644
index 0000000..13133cb
--- /dev/null
+++ b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Copy of progress_indeterminate_horizontal_rect2 in frameworks/base/core/res -->
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="2000"
+ android:propertyXName="translateX"
+ android:pathData="M -197.60001,0 c 14.28182,0 85.07782,0 135.54689,0 c 54.26191,0 90.42461,0 168.24331,0 c 144.72154,0 316.40982,0 316.40982,0 "
+ android:interpolator="@interpolator/progress_indeterminate_horizontal_rect2_translatex_copy"
+ android:repeatCount="infinite" />
+</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_state_off.xml b/packages/SystemUI/res/color/settingslib_state_off.xml
new file mode 100644
index 0000000..e821825
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_state_off.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondaryVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_state_on.xml b/packages/SystemUI/res/color/settingslib_state_on.xml
new file mode 100644
index 0000000..6d2133c
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_state_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_track_off.xml b/packages/SystemUI/res/color/settingslib_track_off.xml
new file mode 100644
index 0000000..21d1dcc
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_track_off.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_track_on.xml b/packages/SystemUI/res/color/settingslib_track_on.xml
new file mode 100644
index 0000000..ba7848a
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_track_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
index 33263a9..0ae5dc7 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
@@ -1,201 +1,142 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2018 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
- -->
+<?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.
+-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
+ xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
- <vector
- android:width="60dp"
- android:height="60dp"
- android:viewportHeight="60"
- android:viewportWidth="60">
+ <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
+ android:viewportWidth="60">
<group android:name="_R_G">
- <group
- android:name="_R_G_L_1_G_N_4_T_0"
- android:translateX="30"
- android:translateY="30">
- <group
- android:name="_R_G_L_1_G"
- android:pivotX="114"
- android:pivotY="114"
- android:scaleX="0.42200000000000004"
- android:scaleY="0.42200000000000004"
- android:translateX="-114"
- android:translateY="-114">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_1_P_0"
- android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_2_P_0"
- android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_3_P_0"
- android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_4_P_0"
- android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
+ <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
+ <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
+ android:translateY="38.75" android:scaleX="1" android:scaleY="1">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="@color/biometric_dialog_error"
+ android:fillAlpha="1" android:fillType="nonZero"
+ android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
</group>
+ <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
+ android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
+ android:scaleX="1" android:scaleY="1">
+ <path android:name="_R_G_L_1_G_D_1_P_0"
+ android:fillColor="@color/biometric_dialog_error"
+ android:fillAlpha="1" android:fillType="nonZero"
+ android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
+ </group>
+ <path android:name="_R_G_L_1_G_D_2_P_0"
+ android:strokeColor="@color/biometric_dialog_error"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2.5" android:strokeAlpha="1"
+ android:trimPathStart="0" android:trimPathEnd="1"
+ android:trimPathOffset="0"
+ android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
</group>
- <group
- android:name="_R_G_L_0_G_N_4_T_0"
- android:translateX="30"
- android:translateY="30">
- <group
- android:name="_R_G_L_0_G"
- android:translateX="-30.05"
- android:translateY="-30">
- <group
- android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
- android:scaleX="1"
- android:scaleY="1"
- android:translateX="30"
- android:translateY="38.75">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/biometric_dialog_error"
- android:fillType="nonZero"
- android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " />
- </group>
- <group
- android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0"
- android:pivotX="0.002"
- android:pivotY="7.488"
- android:scaleX="1"
- android:scaleY="1"
- android:translateX="30"
- android:translateY="25">
- <path
- android:name="_R_G_L_0_G_D_1_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/biometric_dialog_error"
- android:fillType="nonZero"
- android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " />
- </group>
- <path
- android:name="_R_G_L_0_G_D_2_P_0"
- android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_error"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="2.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-10.325"
+ android:translateY="-10.25">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="0" android:trimPathOffset="0"
+ android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
+ <path android:name="_R_G_L_0_G_D_1_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="0" android:trimPathOffset="0"
+ android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
+ <path android:name="_R_G_L_0_G_D_2_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="0" android:trimPathOffset="0"
+ android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
+ <path android:name="_R_G_L_0_G_D_3_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="0" android:trimPathOffset="0"
+ android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
</group>
</group>
- <group android:name="time_group" />
+ <group android:name="time_group"/>
</vector>
</aapt:attr>
- <target android:name="_R_G_L_1_G_D_0_P_0">
+ <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="67"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="67"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="100"
+ android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="100"
+ android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_1_G_D_1_P_0">
+ <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="67"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="67"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="100"
+ android:startOffset="67" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="100"
+ android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
@@ -204,182 +145,58 @@
<target android:name="_R_G_L_1_G_D_2_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="67"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="133"
+ android:startOffset="67" android:valueFrom="1" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_1_G_D_3_P_0">
+ <target android:name="_R_G_L_0_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+ android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_1_G_D_4_P_0">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+ android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleX"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleX"
- android:startOffset="67"
- android:valueFrom="1.1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleY"
- android:startOffset="67"
- android:valueFrom="1.1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleX"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleX"
- android:startOffset="67"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleY"
- android:startOffset="67"
- android:valueFrom="1.1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
@@ -388,26 +205,38 @@
<target android:name="_R_G_L_0_G_D_2_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="67"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="133"
- android:propertyName="trimPathEnd"
- android:startOffset="67"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+ android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+ android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
@@ -416,14 +245,10 @@
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
+ <objectAnimator android:propertyName="translateX" android:duration="417"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType"/>
</set>
</aapt:attr>
</target>
-</animated-vector>
\ No newline at end of file
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
index b899828..fc2c7d0 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
@@ -1,179 +1,170 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2018 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
- -->
+<?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.
+-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
+ xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
- <vector
- android:width="60dp"
- android:height="60dp"
- android:viewportHeight="60"
- android:viewportWidth="60">
+ <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
+ android:viewportWidth="60">
<group android:name="_R_G">
- <group
- android:name="_R_G_L_1_G_N_4_T_0"
- android:translateX="30"
- android:translateY="30">
- <group
- android:name="_R_G_L_1_G"
- android:pivotX="114"
- android:pivotY="114"
- android:scaleX="0.42244"
- android:scaleY="0.42244"
- android:translateX="-114"
- android:translateY="-114">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_1_P_0"
- android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_2_P_0"
- android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_3_P_0"
- android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_4_P_0"
- android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
+ <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
+ <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
+ android:translateY="38.75" android:scaleX="0" android:scaleY="0">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="@color/biometric_dialog_error"
+ android:fillAlpha="1" android:fillType="nonZero"
+ android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
</group>
+ <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
+ android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
+ android:scaleX="1" android:scaleY="0">
+ <path android:name="_R_G_L_1_G_D_1_P_0"
+ android:fillColor="@color/biometric_dialog_error"
+ android:fillAlpha="1" android:fillType="nonZero"
+ android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
+ </group>
+ <path android:name="_R_G_L_1_G_D_2_P_0"
+ android:strokeColor="@color/biometric_dialog_error"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2.5" android:strokeAlpha="1"
+ android:trimPathStart="1" android:trimPathEnd="1"
+ android:trimPathOffset="0"
+ android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
</group>
- <group
- android:name="_R_G_L_0_G_N_4_T_0"
- android:translateX="30"
- android:translateY="30">
- <group
- android:name="_R_G_L_0_G"
- android:translateX="-30.05"
- android:translateY="-30">
- <group
- android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
- android:scaleX="0"
- android:scaleY="0"
- android:translateX="30"
- android:translateY="38.75">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/biometric_dialog_error"
- android:fillType="nonZero"
- android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " />
- </group>
- <group
- android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0"
- android:pivotX="0.002"
- android:pivotY="7.488"
- android:scaleX="1"
- android:scaleY="0"
- android:translateX="30"
- android:translateY="25">
- <path
- android:name="_R_G_L_0_G_D_1_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/biometric_dialog_error"
- android:fillType="nonZero"
- android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " />
- </group>
- <path
- android:name="_R_G_L_0_G_D_2_P_0"
- android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_error"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="2.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="1" />
- </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-10.325"
+ android:translateY="-10.25">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="1" android:trimPathOffset="0"
+ android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
+ <path android:name="_R_G_L_0_G_D_1_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="1" android:trimPathOffset="0"
+ android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
+ <path android:name="_R_G_L_0_G_D_2_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="1" android:trimPathOffset="0"
+ android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
+ <path android:name="_R_G_L_0_G_D_3_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="1" android:trimPathOffset="0"
+ android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
</group>
</group>
- <group android:name="time_group" />
+ <group android:name="time_group"/>
</vector>
</aapt:attr>
- <target android:name="_R_G_L_1_G_D_0_P_0">
+ <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="100"
+ android:startOffset="167" android:valueFrom="0"
+ android:valueTo="1.1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="100"
+ android:startOffset="167" android:valueFrom="0"
+ android:valueTo="1.1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="67"
+ android:startOffset="267" android:valueFrom="1.1"
+ android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="67"
+ android:startOffset="267" android:valueFrom="1.1"
+ android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_1_G_D_1_P_0">
+ <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="167"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="100"
+ android:startOffset="167" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="100"
+ android:startOffset="167" android:valueFrom="0"
+ android:valueTo="1.1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="67"
+ android:startOffset="267" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="67"
+ android:startOffset="267" android:valueFrom="1.1"
+ android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
@@ -182,193 +173,37 @@
<target android:name="_R_G_L_1_G_D_2_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="267"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_1_G_D_3_P_0">
+ <target android:name="_R_G_L_0_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_1_G_D_4_P_0">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleX"
- android:startOffset="167"
- android:valueFrom="0"
- android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleY"
- android:startOffset="167"
- android:valueFrom="0"
- android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleX"
- android:startOffset="267"
- android:valueFrom="1.1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleY"
- android:startOffset="267"
- android:valueFrom="1.1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleX"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleX"
- android:startOffset="167"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleY"
- android:startOffset="167"
- android:valueFrom="0"
- android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleX"
- android:startOffset="267"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleY"
- android:startOffset="267"
- android:valueFrom="1.1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
@@ -377,15 +212,24 @@
<target android:name="_R_G_L_0_G_D_2_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="267"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
@@ -394,14 +238,10 @@
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
+ <objectAnimator android:propertyName="translateX" android:duration="350"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType"/>
</set>
</aapt:attr>
</target>
-</animated-vector>
\ No newline at end of file
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_forward.xml b/packages/SystemUI/res/drawable/ic_arrow_forward.xml
new file mode 100644
index 0000000..438e4c7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_forward.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:autoMirrored="true"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M6.23,20.23l1.77,1.77l10,-10l-10,-10l-1.77,1.77l8.23,8.23z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml
new file mode 100644
index 0000000..2c34060
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10C20,8.9 19.1,8 18,8zM9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2H9V6zM18,20H6V10h12V20zM12,17c1.1,0 2,-0.9 2,-2c0,-1.1 -0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2C10,16.1 10.9,17 12,17z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_home_devices.xml b/packages/SystemUI/res/drawable/ic_media_home_devices.xml
new file mode 100644
index 0000000..886c64d9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_home_devices.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4c-1.1,0 -2,0.9 -2,2v11c0,1.1 0.9,2 2,2h4v2h3v-4H4V6h16v1h2V6c0,-1.1 -0.9,-2 -2,-2z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17.5,16.5m-2.33,0a2.33,2.33 0,1 1,4.66 0a2.33,2.33 0,1 1,-4.66 0"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M21,8h-7c-0.55,0 -1,0.45 -1,1v11c0,0.55 0.45,1 1,1h7c0.55,0 1,-0.45 1,-1L22,9c0,-0.55 -0.45,-1 -1,-1zM17.5,9c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5 -1.5,-0.67 -1.5,-1.5 0.67,-1.5 1.5,-1.5zM17.5,20c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings_24dp.xml b/packages/SystemUI/res/drawable/ic_settings_24dp.xml
new file mode 100644
index 0000000..ac4c43b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_settings_24dp.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml
new file mode 100644
index 0000000..f38a368
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22l16,0l0,-2l-11,0l13,-13l0,1l2,0l0,-6z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,10h2v8h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,20h2v2h-2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_background.xml b/packages/SystemUI/res/drawable/internet_dialog_background.xml
new file mode 100644
index 0000000..3ceb0f6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <shape android:shape="rectangle">
+ <corners android:radius="8dp" />
+ <solid android:color="?android:attr/colorBackground" />
+ </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
new file mode 100644
index 0000000..50267fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"
+ android:width="1dp"/>
+ <corners android:radius="20dp"/>
+ <padding
+ android:left="8dp"
+ android:right="8dp"
+ android:top="4dp"
+ android:bottom="4dp" />
+ <solid android:color="@android:color/transparent" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
new file mode 100644
index 0000000..14672ef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/internet_dialog_corner_radius"
+ android:topRightRadius="@dimen/internet_dialog_corner_radius"
+ android:bottomLeftRadius="@dimen/internet_dialog_corner_radius"
+ android:bottomRightRadius="@dimen/internet_dialog_corner_radius"/>
+ <solid android:color="?android:attr/colorBackground" />
+ </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/logout_button_background.xml b/packages/SystemUI/res/drawable/logout_button_background.xml
index eafd663..34434be 100644
--- a/packages/SystemUI/res/drawable/logout_button_background.xml
+++ b/packages/SystemUI/res/drawable/logout_button_background.xml
@@ -17,7 +17,8 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="@color/logout_button_bg_color"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
<corners android:radius="@dimen/logout_button_corner_radius"/>
</shape>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
index 6725a26..e626675 100644
--- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
@@ -14,19 +14,20 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <solid
- android:color="?androidprv:attr/colorSurface"
- />
- <corners android:radius="20dp" />
-
- <padding
- android:left="20dp"
- android:right="20dp">
- </padding>
-
-</shape>
\ No newline at end of file
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <inset
+ android:insetBottom="4dp"
+ android:insetTop="4dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="20dp" />
+ <padding
+ android:left="20dp"
+ android:right="20dp" />
+ <solid android:color="?androidprv:attr/colorSurface" />
+ </shape>
+ </inset>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
new file mode 100644
index 0000000..95209f8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Copy of progress_indeterminate_horizontal_material_trimmed in frameworks/base/core/res -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/vector_drawable_progress_indeterminate_horizontal_trimmed" >
+ <target
+ android:name="rect_grp"
+ android:animation="@anim/progress_indeterminate_horizontal_rect" />
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml
similarity index 100%
copy from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml
copy to packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
similarity index 100%
copy from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml
copy to packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml
new file mode 100644
index 0000000..b41762f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid
+ android:color="@color/settingslib_thumb_off_color"
+ android:alpha="?android:attr/disabledAlpha"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_off.xml b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml
new file mode 100644
index 0000000..87d4aea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid android:color="@color/settingslib_thumb_off_color"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
new file mode 100644
index 0000000..5566ea3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid android:color="@color/settingslib_state_on_color"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml
new file mode 100644
index 0000000..06bb779
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/settingslib_thumb_on" android:state_checked="true"/>
+ <item android:drawable="@drawable/settingslib_thumb_off" android:state_checked="false"/>
+ <item android:drawable="@drawable/settingslib_thumb_disabled" android:state_enabled="false"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml
new file mode 100644
index 0000000..15dfcb7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <solid
+ android:color="@color/settingslib_track_off_color"
+ android:alpha="?android:attr/disabledAlpha"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_off_background.xml b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
new file mode 100644
index 0000000..3a09284
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <padding android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"/>
+ <solid android:color="@color/settingslib_track_off_color"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
new file mode 100644
index 0000000..1d9dacd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <padding android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"/>
+ <solid android:color="@color/settingslib_track_on_color"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_selector.xml b/packages/SystemUI/res/drawable/settingslib_track_selector.xml
new file mode 100644
index 0000000..a38c3b4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/settingslib_track_on_background" android:state_checked="true"/>
+ <item android:drawable="@drawable/settingslib_track_off_background" android:state_checked="false"/>
+ <item android:drawable="@drawable/settingslib_track_disabled_background" android:state_enabled="false"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
new file mode 100644
index 0000000..aec204f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Variant of vector_drawable_progress_indeterminate_horizontal in frameworks/base/core/res, which
+ draws the whole height of the progress bar instead having blank space above and below the
+ bar. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:height="10dp"
+ android:width="340dp"
+ android:viewportHeight="10"
+ android:viewportWidth="340" >
+ <group
+ android:name="progress_group"
+ android:translateX="180"
+ android:translateY="5" >
+ <path
+ android:name="background_track"
+ android:pathData="M -180.0,-5.0 l 360.0,0 l 0,10.0 l -360.0,0 Z"
+ android:fillColor="?androidprv:attr/colorSurfaceVariant"/>
+ <group
+ android:name="rect_grp"
+ android:translateX="-197.60001"
+ android:scaleX="0.5" >
+ <path
+ android:name="rect"
+ android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"
+ android:fillColor="?androidprv:attr/colorAccentPrimaryVariant" />
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_toast.xml b/packages/SystemUI/res/layout/global_actions_toast.xml
new file mode 100644
index 0000000..1f08996
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_toast.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center|bottom"
+ android:gravity="center"
+ android:layout_marginBottom="@dimen/global_actions_info_margin"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="382dp"
+ android:layout_weight="0"
+ android:background="@drawable/global_actions_lite_background"
+ android:theme="@style/Theme.SystemUI.QuickSettings"
+ android:paddingTop="14dp"
+ android:paddingBottom="14dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textSize="14sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/global_action_smart_lock_disabled" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml
index e4a9694..6a9254c 100644
--- a/packages/SystemUI/res/layout/global_screenshot_static.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_static.xml
@@ -36,7 +36,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/screenshot_action_container_offset_y"
android:paddingEnd="@dimen/screenshot_action_container_padding_right"
android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
android:elevation="1dp"
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
new file mode 100644
index 0000000..c88e95f
--- /dev/null
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -0,0 +1,388 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/internet_connectivity_dialog"
+ android:layout_width="@dimen/internet_dialog_list_max_width"
+ android:layout_height="@dimen/internet_dialog_list_max_height"
+ android:background="@drawable/internet_dialog_rounded_top_corner_background"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Widget.SliceView.Panel"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="@dimen/internet_dialog_network_layout_margin"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/internet_dialog_title"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="32dp"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="24sp"/>
+
+ <TextView
+ android:id="@+id/internet_dialog_subtitle"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="20dp"
+ android:layout_marginTop="4dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearance.InternetDialog.Secondary"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/internet_dialog_network_layout_margin"
+ android:orientation="vertical">
+
+ <View
+ android:id="@+id/divider"
+ android:layout_gravity="center_vertical|center_horizontal"
+ android:layout_width="340dp"
+ android:layout_height="4dp"
+ android:background="?androidprv:attr/colorSurfaceVariant"/>
+
+ <ProgressBar
+ android:id="@+id/wifi_searching_progress"
+ android:indeterminate="true"
+ android:layout_width="340dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:visibility="gone"
+ style="@style/TrimmedHorizontalProgressBar"/>
+ </LinearLayout>
+
+ <androidx.core.widget.NestedScrollView
+ android:id="@+id/scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:id="@+id/scroll_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/ethernet_layout"
+ style="@style/InternetDialog.Network"
+ android:background="@drawable/settingslib_switch_bar_bg_on"
+ android:visibility="gone">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical|start"
+ android:clickable="false">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:autoMirrored="true"
+ android:src="@drawable/stat_sys_ethernet_fully"
+ android:tint="@color/connected_network_primary_color"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical"
+ android:orientation="vertical"
+ android:clickable="false">
+ <TextView
+ android:text="@string/ethernet_label"
+ style="@style/InternetDialog.NetworkTitle.Active"/>
+ <TextView
+ android:text="@string/to_switch_networks_disconnect_ethernet"
+ style="@style/InternetDialog.NetworkSummary.Active"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/mobile_network_layout"
+ style="@style/InternetDialog.Network">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/signal_icon"
+ android:autoMirrored="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/mobile_title"
+ style="@style/InternetDialog.NetworkTitle"/>
+ <TextView
+ android:id="@+id/mobile_summary"
+ style="@style/InternetDialog.NetworkSummary"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="48dp"
+ android:layout_gravity="end|center_vertical">
+ <Switch
+ android:id="@+id/mobile_toggle"
+ android:contentDescription="@string/mobile_data_settings_title"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="match_parent"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/turn_on_wifi_layout"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:gravity="center"
+ android:clickable="false"
+ android:focusable="false">
+
+ <FrameLayout
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/wifi_toggle_title"
+ android:text="@string/turn_on_wifi"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:textAppearance="@style/TextAppearance.InternetDialog"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="48dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp">
+ <Switch
+ android:id="@+id/wifi_toggle"
+ android:contentDescription="@string/turn_on_wifi"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="match_parent"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/wifi_connected_layout"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="24dp"
+ android:background="@drawable/settingslib_switch_bar_bg_on"
+ android:visibility="gone">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/wifi_connected_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="72dp"
+ android:layout_marginEnd="30dp"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/wifi_connected_title"
+ style="@style/InternetDialog.NetworkTitle.Active"
+ android:textSize="14sp"/>
+ <TextView
+ android:id="@+id/wifi_connected_summary"
+ style="@style/InternetDialog.NetworkSummary.Active"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="match_parent"
+ android:clickable="false"
+ android:layout_gravity="end|center_vertical"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/wifi_settings_icon"
+ android:src="@drawable/ic_settings_24dp"
+ android:layout_width="24dp"
+ android:layout_gravity="end|center_vertical"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/wifi_list_layout"
+ android:scrollbars="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:overScrollMode="never"
+ android:nestedScrollingEnabled="false"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/see_all_layout"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical|center_horizontal"
+ android:orientation="horizontal"
+ android:paddingStart="22dp"
+ android:paddingEnd="22dp">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ <ImageView
+ android:id="@+id/arrow_forward"
+ android:src="@drawable/ic_arrow_forward"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ <TextView
+ android:text="@string/see_all_networks"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="14sp"/>
+ </FrameLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/wifi_scan_notify_layout"
+ style="@style/InternetDialog.Network"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:paddingBottom="4dp"
+ android:clickable="false"
+ android:focusable="false">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:src="@drawable/ic_info_outline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="?android:attr/textColorTertiary"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/wifi_scan_notify_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:clickable="true"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/done_layout"
+ android:layout_width="67dp"
+ android:layout_height="48dp"
+ android:layout_marginEnd="24dp"
+ android:layout_marginBottom="40dp"
+ android:layout_gravity="end|center_vertical"
+ android:clickable="true"
+ android:focusable="true">
+ <Button
+ android:text="@string/inline_done_button"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="match_parent"
+ android:layout_height="36dp"
+ android:layout_gravity="center"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="14sp"
+ android:background="@drawable/internet_dialog_footer_background"
+ android:clickable="false"/>
+ </FrameLayout>
+ </LinearLayout>
+ </androidx.core.widget.NestedScrollView>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
new file mode 100644
index 0000000..868331e
--- /dev/null
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/internet_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/wifi_list"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="24dp">
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/wifi_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/wifi_network_layout"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="72dp"
+ android:layout_marginEnd="30dp"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/wifi_title"
+ style="@style/InternetDialog.NetworkTitle"
+ android:textSize="14sp"/>
+ <TextView
+ android:id="@+id/wifi_summary"
+ style="@style/InternetDialog.NetworkSummary"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="match_parent"
+ android:clickable="false"
+ android:layout_gravity="end|center_vertical">
+ <ImageView
+ android:id="@+id/wifi_end_icon"
+ android:layout_gravity="end|center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index a21a63c..9cf09ff 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -15,19 +15,25 @@
~ limitations under the License
-->
<!-- This is a view that shows a user switcher in Keyguard. -->
-<com.android.systemui.statusbar.phone.UserAvatarView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyguard_qs_user_switch_view"
- android:layout_width="@dimen/kg_framed_avatar_size"
- android:layout_height="@dimen/kg_framed_avatar_size"
- android:layout_centerHorizontal="true"
- android:layout_gravity="top|end"
- android:layout_marginEnd="16dp"
- systemui:avatarPadding="0dp"
- systemui:badgeDiameter="18dp"
- systemui:badgeMargin="1dp"
- systemui:frameColor="@color/kg_user_avatar_frame"
- systemui:framePadding="0dp"
- systemui:frameWidth="0dp">
-</com.android.systemui.statusbar.phone.UserAvatarView>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end">
+ <com.android.systemui.statusbar.phone.UserAvatarView
+ android:id="@+id/kg_multi_user_avatar"
+ android:layout_width="@dimen/kg_framed_avatar_size"
+ android:layout_height="@dimen/kg_framed_avatar_size"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="16dp"
+ systemui:avatarPadding="0dp"
+ systemui:badgeDiameter="18dp"
+ systemui:badgeMargin="1dp"
+ systemui:frameColor="@color/kg_user_avatar_frame"
+ systemui:framePadding="0dp"
+ systemui:frameWidth="0dp">
+ </com.android.systemui.statusbar.phone.UserAvatarView>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index eb76382..850b017 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -30,7 +30,6 @@
android:id="@+id/status_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:paddingEnd="@dimen/system_icons_keyguard_padding_end"
android:paddingTop="@dimen/status_bar_padding_top"
android:layout_alignParentEnd="true"
android:gravity="center_vertical|end" >
@@ -39,6 +38,7 @@
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginStart="@dimen/system_icons_super_container_margin_start"
+ android:layout_marginEnd="@dimen/status_bar_padding_end"
android:gravity="center_vertical|end">
<include layout="@layout/system_icons" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index c7e54d4..c3fc669 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -48,20 +48,41 @@
android:id="@+id/recommendation_card_icon"
android:layout_width="@dimen/qs_media_icon_size"
android:layout_height="@dimen/qs_media_icon_size"
+ android:layout_marginTop="@dimen/qs_media_padding"
android:src="@drawable/ic_headset"
- style="@style/MediaPlayer.AppIcon"/>
+ style="@style/MediaPlayer.AppIcon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="0"/>
<TextView
android:id="@+id/recommendation_card_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:maxLines="2"
+ android:maxLines="1"
android:text="@string/controls_media_smartspace_rec_title"
android:fontFamily="google-sans-medium"
android:textDirection="locale"
android:textSize="@dimen/qq_aa_media_rec_header_text_size"
- android:breakStrategy="balanced"
- android:hyphenationFrequency="none"/>
+ android:hyphenationFrequency="none"
+ app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <View
+ android:id="@+id/recommendation_gradient_view"
+ android:layout_width="@dimen/qs_aa_media_gradient_bg_width"
+ android:layout_height="0dp"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:background="@drawable/qs_media_recommendation_bg_gradient"
+ app:layout_constraintTop_toTopOf="@id/recommendation_card_text"
+ app:layout_constraintBottom_toBottomOf="@id/recommendation_card_text"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="1"/>
<FrameLayout
android:id="@+id/media_cover1_container"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 075473a..566cd25 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -163,18 +163,6 @@
</LinearLayout>
</LinearLayout>
- <ImageView
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:tint="?android:attr/textColor"
- android:src="@drawable/ic_cast_connected"
- android:forceHasOverlappingRendering="false" />
-
<!-- Seek Bar -->
<!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
<SeekBar
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index 459fb66..ee4530c 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -24,9 +24,9 @@
android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
android:layout_marginTop="8dp"
android:orientation="vertical"
- android:paddingBottom="12dp"
- android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:paddingTop="12dp"
+ android:paddingHorizontal="@dimen/ongoing_appops_dialog_side_padding"
android:background="@drawable/qs_dialog_bg"
/>
-<!-- 12dp padding bottom so there's 20dp total under the icon -->
-<!-- 8dp padding top, as there's 4dp margin in each row -->
\ No newline at end of file
+<!-- 8dp padding bottom so there's 16dp total under the icon -->
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml
index 7c8945e..e1f0793 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_item.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml
@@ -24,8 +24,6 @@
android:layout_marginTop="4dp"
android:importantForAccessibility="yes"
android:background="?android:attr/selectableItemBackground"
- android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
- android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
android:focusable="true"
>
<!-- 4dp marginTop makes 20dp minimum between icons -->
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 5b9ca1b..42a7c89 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -26,17 +26,38 @@
android:focusable="true"
android:theme="@style/Theme.SystemUI.QuickSettings.Header">
- <com.android.systemui.statusbar.policy.Clock
- android:id="@+id/clock"
+ <LinearLayout
+ android:id="@+id/clock_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="48dp"
- android:minHeight="48dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center_vertical|start"
android:gravity="center_vertical|start"
- android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
- android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.QS.Status" />
+ >
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minHeight="48dp"
+ android:gravity="center_vertical|start"
+ android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status" />
+
+ <com.android.systemui.statusbar.policy.VariableDateView
+ android:id="@+id/date_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/status_bar_left_clock_end_padding"
+ android:gravity="center_vertical|start"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status"
+ systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ systemui:shortDatePattern="@string/abbrev_month_day_no_year"
+ />
+ </LinearLayout>
<include layout="@layout/qs_carrier_group"
android:id="@+id/carrier_group"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index bff93a9..b1e8c38 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -36,7 +36,7 @@
android:layout_weight="1"
android:gravity="center_vertical|start" >
- <com.android.systemui.statusbar.policy.DateView
+ <com.android.systemui.statusbar.policy.VariableDateView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -44,9 +44,16 @@
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.QS.Status"
- systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+ systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ systemui:shortDatePattern="@string/abbrev_month_day_no_year"
+ />
</FrameLayout>
+ <!-- We want this to be centered (to align with notches). In order to do that, the following
+ has to hold (in portrait):
+ * date_container and privacy_container must have the same width and weight
+ * header_text_container must be gone
+ -->
<android.widget.Space
android:id="@+id/space"
android:layout_width="0dp"
@@ -73,7 +80,7 @@
android:layout_weight="1"
android:gravity="center_vertical|end" >
- <include layout="@layout/ongoing_privacy_chip" />
+ <include layout="@layout/ongoing_privacy_chip" />
</FrameLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index d1cc01f..c122829 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -113,6 +113,8 @@
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="center_vertical"
android:text="@string/screenrecord_taps_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="@*android:string/config_headlineFontFamily"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 6d5be30..8b78732 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -54,8 +54,7 @@
<com.android.keyguard.LockIconView
android:id="@+id/lock_icon_view"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center">
+ android:layout_height="wrap_content">
<!-- Background protection -->
<ImageView
android:id="@+id/lock_icon_bg"
@@ -71,6 +70,19 @@
android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"/>
+
+ <!-- Fingerprint -->
+ <!-- AOD dashed fingerprint icon with moving dashes -->
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/lock_udfps_aod_fp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/lock_icon_padding"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ systemui:lottie_autoPlay="false"
+ systemui:lottie_loop="true"
+ systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
</com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
@@ -114,6 +126,7 @@
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/close_handle_underlap"
+ android:importantForAccessibility="no"
systemui:layout_constraintStart_toStartOf="parent"
systemui:layout_constraintEnd_toEndOf="parent"
/>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index 412276d..bbb8df1c 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -30,8 +30,8 @@
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/manage_text"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginTop="16dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
android:layout_gravity="start"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
@@ -43,8 +43,8 @@
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginTop="16dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
android:layout_gravity="end"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index bea50e8..71e8fc9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -62,8 +62,7 @@
<com.android.systemui.statusbar.LightRevealScrim
android:id="@+id/light_reveal_scrim"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone" />
+ android:layout_height="match_parent" />
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/udfps_enroll_view.xml b/packages/SystemUI/res/layout/udfps_enroll_view.xml
index f1ff6d6..e41a632 100644
--- a/packages/SystemUI/res/layout/udfps_enroll_view.xml
+++ b/packages/SystemUI/res/layout/udfps_enroll_view.xml
@@ -20,10 +20,23 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <!-- The layout height/width are placeholders, which will be overwritten by
+ FingerprintSensorPropertiesInternal. -->
+ <View
+ android:id="@+id/udfps_enroll_accessibility_view"
+ android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/accessibility_fingerprint_label"/>
+
+ <ImageView
+ android:id="@+id/udfps_enroll_animation_fp_progress_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
<!-- Fingerprint -->
<ImageView
android:id="@+id/udfps_enroll_animation_fp_view"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@string/accessibility_fingerprint_label"/>
+ android:layout_height="match_parent"/>
</com.android.systemui.biometrics.UdfpsEnrollView>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 687830d..0fcbfa1 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -20,7 +20,7 @@
android:id="@+id/udfps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- systemui:sensorTouchAreaCoefficient="0.75"
+ systemui:sensorTouchAreaCoefficient="1.0"
android:contentDescription="@string/accessibility_fingerprint_label">
<ViewStub
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 19ec8ce..f057603 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -16,7 +16,7 @@
<resources>
<!-- Minimum margin between clock and top of screen or ambient indication -->
- <dimen name="keyguard_clock_top_margin">76dp</dimen>
+ <dimen name="keyguard_clock_top_margin">38dp</dimen>
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 461505f..ffcc3a8 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -48,4 +48,16 @@
<item name="android:windowLightStatusBar">false</item>
</style>
+ <style name="TextAppearance.InternetDialog.Active">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/connected_network_primary_color</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Secondary.Active">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@color/connected_network_secondary_color</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index da80b85..0a34dfd 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -95,4 +95,7 @@
<dimen name="controls_top_margin">24dp</dimen>
<dimen name="global_actions_grid_item_layout_height">80dp</dimen>
+
+ <!-- Internet panel related dimensions -->
+ <dimen name="internet_dialog_list_max_width">624dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index b5337d3..3121ce3 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -69,6 +69,10 @@
<declare-styleable name="DateView">
<attr name="datePattern" format="string" />
</declare-styleable>
+ <declare-styleable name="VariableDateView">
+ <attr name="longDatePattern" format="string" />
+ <attr name="shortDatePattern" format="string" />
+ </declare-styleable>
<declare-styleable name="PseudoGridView">
<attr name="numColumns" format="integer" />
<attr name="verticalSpacing" format="dimension" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 2260d21..f539b0c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-->
-<resources>
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<drawable name="notification_number_text_color">#ffffffff</drawable>
<drawable name="ticker_background_color">#ff1d1d1d</drawable>
<drawable name="system_bar_background">@color/system_bar_background_opaque</drawable>
@@ -187,10 +187,8 @@
<!-- UDFPS colors -->
<color name="udfps_enroll_icon">#000000</color> <!-- 100% black -->
<color name="udfps_moving_target_fill">#cc4285f4</color> <!-- 80% blue -->
- <color name="udfps_enroll_progress">#ff669DF6</color> <!-- 100% blue -->
-
- <!-- Logout button -->
- <color name="logout_button_bg_color">#ccffffff</color>
+ <color name="udfps_enroll_progress">#ff669DF6</color> <!-- blue 400 -->
+ <color name="udfps_enroll_progress_help">#ffEE675C</color> <!-- red 400 -->
<!-- Color for the Assistant invocation lights -->
<color name="default_invocation_lights_color">#ffffffff</color> <!-- white -->
@@ -281,4 +279,16 @@
<color name="wallet_card_border">#33FFFFFF</color>
<color name="people_tile_background">@android:color/system_accent2_50</color>
+
+ <!-- Internet Dialog -->
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_state_off</color>
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_track_on</color>
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_track_off</color>
+ <color name="connected_network_primary_color">#191C18</color>
+ <color name="connected_network_secondary_color">#41493D</color>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6ad9ab9..c231afc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -90,7 +90,7 @@
<dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
<!-- End padding for left-aligned status bar clock -->
- <dimen name="status_bar_left_clock_end_padding">7dp</dimen>
+ <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
<!-- Spacing after the wifi signals that is present if there are any icons following it. -->
<dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
@@ -331,11 +331,10 @@
<dimen name="global_screenshot_x_scale">80dp</dimen>
<dimen name="screenshot_bg_protection_height">242dp</dimen>
<dimen name="screenshot_preview_elevation">4dp</dimen>
- <dimen name="screenshot_offset_y">24dp</dimen>
+ <dimen name="screenshot_offset_y">8dp</dimen>
<dimen name="screenshot_offset_x">16dp</dimen>
<dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
<dimen name="screenshot_dismiss_button_margin">8dp</dimen>
- <dimen name="screenshot_action_container_offset_y">16dp</dimen>
<dimen name="screenshot_action_container_corner_radius">18dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
@@ -744,9 +743,7 @@
<!-- The margin between the status view and the notifications on Keyguard.-->
<dimen name="keyguard_status_view_bottom_margin">20dp</dimen>
<!-- Minimum margin between clock and status bar -->
- <dimen name="keyguard_clock_top_margin">36dp</dimen>
- <!-- The margin between top of clock and bottom of lock icon. -->
- <dimen name="keyguard_clock_lock_margin">16dp</dimen>
+ <dimen name="keyguard_clock_top_margin">18dp</dimen>
<!-- The amount to shift the clocks during a small/large transition -->
<dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
@@ -1150,9 +1147,9 @@
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
<!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
- direction that elements aer moved to prevent burn-in on AOD-->
- <dimen name="udfps_burn_in_offset_x">2dp</dimen>
- <dimen name="udfps_burn_in_offset_y">8dp</dimen>
+ direction that elements are moved to prevent burn-in on AOD-->
+ <dimen name="udfps_burn_in_offset_x">7px</dimen>
+ <dimen name="udfps_burn_in_offset_y">28px</dimen>
<dimen name="corner_size">8dp</dimen>
<dimen name="top_padding">0dp</dimen>
@@ -1243,10 +1240,7 @@
<integer name="wired_charging_keyguard_text_animation_distance">-30</integer>
<!-- Logout button -->
- <dimen name="logout_button_layout_height">32dp</dimen>
- <dimen name="logout_button_padding_horizontal">16dp</dimen>
- <dimen name="logout_button_margin_bottom">12dp</dimen>
- <dimen name="logout_button_corner_radius">4dp</dimen>
+ <dimen name="logout_button_corner_radius">50dp</dimen>
<!-- Blur radius on status bar window and power menu -->
<dimen name="min_window_blur_radius">1px</dimen>
@@ -1277,7 +1271,7 @@
<dimen name="ongoing_appops_dialog_circle_size">32dp</dimen>
- <dimen name="ongoing_appops_dialog_icon_size">20dp</dimen>
+ <dimen name="ongoing_appops_dialog_icon_size">16dp</dimen>
<dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
@@ -1299,8 +1293,6 @@
<dimen name="qs_media_action_margin">12dp</dimen>
<dimen name="qs_seamless_height">24dp</dimen>
<dimen name="qs_seamless_icon_size">12dp</dimen>
- <dimen name="qs_seamless_fallback_icon_size">@dimen/qs_seamless_icon_size</dimen>
- <dimen name="qs_seamless_fallback_margin">20dp</dimen>
<dimen name="qs_footer_horizontal_margin">22dp</dimen>
<dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
<dimen name="qs_media_enabled_seekbar_height">2dp</dimen>
@@ -1310,6 +1302,7 @@
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_aa_media_rec_album_size_collapsed">72dp</dimen>
<dimen name="qs_aa_media_rec_album_size_expanded">76dp</dimen>
+ <dimen name="qs_aa_media_gradient_bg_width">32dp</dimen>
<dimen name="qs_aa_media_rec_album_margin">8dp</dimen>
<dimen name="qs_aa_media_rec_album_margin_vert">4dp</dimen>
<dimen name="qq_aa_media_rec_header_text_size">16sp</dimen>
@@ -1462,6 +1455,10 @@
<!-- Maximum overshoot for the pulse expansion -->
<dimen name="pulse_expansion_max_top_overshoot">32dp</dimen>
+ <!-- Alpha in duration in ms for the auth ripple to become fully vislble. If set to 0,
+ it is immediately visible. -->
+ <integer name="auth_ripple_alpha_in_duration">100</integer>
+
<dimen name="people_space_widget_radius">28dp</dimen>
<dimen name="people_space_image_radius">20dp</dimen>
<dimen name="people_space_messages_count_radius">12dp</dimen>
@@ -1586,4 +1583,42 @@
<!-- The padding between the icon and the text. -->
<dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
<dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+
+ <!-- Internet panel related dimensions -->
+ <dimen name="internet_dialog_list_margin">12dp</dimen>
+ <dimen name="internet_dialog_list_max_height">646dp</dimen>
+ <dimen name="internet_dialog_list_max_width">@dimen/match_parent</dimen>
+
+ <!-- Signal icon in internet dialog -->
+ <dimen name="signal_strength_icon_size">24dp</dimen>
+
+ <!-- Internet dialog related dimensions -->
+ <dimen name="internet_dialog_corner_radius">24dp</dimen>
+ <!-- End margin of network layout -->
+ <dimen name="internet_dialog_network_layout_margin">16dp</dimen>
+ <!-- Size of switch bar in internet dialog -->
+ <dimen name="settingslib_switchbar_margin">16dp</dimen>
+ <!-- Minimum width of switch -->
+ <dimen name="settingslib_min_switch_width">52dp</dimen>
+ <!-- Size of layout margin left -->
+ <dimen name="settingslib_switchbar_padding_left">20dp</dimen>
+ <!-- Size of layout margin right -->
+ <dimen name="settingslib_switchbar_padding_right">20dp</dimen>
+ <!-- Radius of switch bar -->
+ <dimen name="settingslib_switch_bar_radius">35dp</dimen>
+ <!-- Margin of switch thumb -->
+ <dimen name="settingslib_switch_thumb_margin">4dp</dimen>
+ <!-- Size of switch thumb -->
+ <dimen name="settingslib_switch_thumb_size">20dp</dimen>
+ <!-- Width of switch track -->
+ <dimen name="settingslib_switch_track_width">52dp</dimen>
+ <!-- Height of switch track -->
+ <dimen name="settingslib_switch_track_height">28dp</dimen>
+ <!-- Radius of switch track -->
+ <dimen name="settingslib_switch_track_radius">35dp</dimen>
+
+ <!-- Height percentage of the parent container occupied by the communal view -->
+ <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
+
+ <dimen name="drag_and_drop_icon_size">70dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a670216..4ad4fa9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -211,6 +211,8 @@
<!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
<string name="global_action_screenshot">Screenshot</string>
+ <!-- Message shown in power menu when smart lock has been disabled [CHAR_LIMIT=NONE] -->
+ <string name="global_action_smart_lock_disabled">Smart Lock disabled</string>
<!-- text to show in place of RemoteInput images when they cannot be shown.
[CHAR LIMIT=50] -->
@@ -1006,7 +1008,7 @@
<string name="sensor_privacy_start_use_mic_camera_dialog_content">This unblocks access for all apps and services allowed to use your camera or microphone.</string>
<!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
- <string name="media_seamless_remote_device">Device</string>
+ <string name="media_seamless_other_device">Other device</string>
<!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] -->
<string name="quick_step_accessibility_toggle_overview">Toggle Overview</string>
@@ -1777,6 +1779,8 @@
<string name="tuner_full_importance_settings">Power notification controls</string>
<string name="tuner_full_importance_settings_on">On</string>
<string name="tuner_full_importance_settings_off">Off</string>
+ <!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description -->
+ <string name="rotation_lock_camera_rotation_on">On - Face-based</string>
<string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications.
\n\n<b>Level 5</b>
\n- Show at the top of the notification list
@@ -2992,4 +2996,43 @@
<string name="global_actions_change_description" translatable="false"><xliff:g>%1$s</xliff:g></string>
<!-- URL for more information about changes in global actions -->
<string name="global_actions_change_url" translatable="false"></string>
+
+ <!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
+ <string name="mobile_data_settings_title">Mobile data</string>
+ <!-- Provider Model: Summary text separator for preferences including a short description
+ (eg. "Connected / 5G"). [CHAR LIMIT=50] -->
+ <string name="preference_summary_default_combination"><xliff:g id="state" example="Connected">%1$s</xliff:g> / <xliff:g id="networkMode" example="LTE">%2$s</xliff:g></string>
+ <!-- Provider Model:
+ Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
+ <string name="mobile_data_connection_active">Connected</string>
+ <!-- Provider Model:
+ Summary indicating that a SIM has no mobile data connection [CHAR LIMIT=50] -->
+ <string name="mobile_data_off_summary">Mobile data won\u0027t auto\u2011connect</string>
+ <!-- Provider Model:
+ Summary indicating that a active SIM and no network available [CHAR LIMIT=50] -->
+ <string name="mobile_data_no_connection">No connection</string>
+ <!-- Provider Model: Summary indicating that no other networks available [CHAR LIMIT=50] -->
+ <string name="non_carrier_network_unavailable">No other networks available</string>
+ <!-- Provider Model: Summary indicating that no networks available [CHAR LIMIT=50] -->
+ <string name="all_network_unavailable">No networks available</string>
+ <!-- Provider Model: Panel title text for turning on the Wi-Fi networks. [CHAR LIMIT=40] -->
+ <string name="turn_on_wifi">Wi\u2011Fi</string>
+ <!-- Provider Model: Title for detail page of wifi network [CHAR LIMIT=30] -->
+ <string name="pref_title_network_details" msgid="7329759534269363308">"Network details"</string>
+ <!-- Provider Model: Panel subtitle for tapping a network to connect to internet. [CHAR LIMIT=60] -->
+ <string name="tap_a_network_to_connect">Tap a network to connect</string>
+ <!-- Provider Model: Panel subtitle for unlocking screen to view networks. [CHAR LIMIT=60] -->
+ <string name="unlock_to_view_networks">Unlock to view networks</string>
+ <!-- Provider Model: Wi-Fi settings. text displayed when Wi-Fi is on and network list is empty [CHAR LIMIT=50]-->
+ <string name="wifi_empty_list_wifi_on">Searching for networks\u2026</string>
+ <!-- Provider Model: Failure notification for connect -->
+ <string name="wifi_failed_connect_message">Failed to connect to network</string>
+ <!-- Provider Model: Toast message for when the user selects cellular as the internet provider and Wi-Fi auto-connect is temporarily disabled [CHAR LIMIT=60]-->
+ <string name="wifi_wont_autoconnect_for_now">Wi\u2011Fi won\u2019t auto-connect for now</string>
+ <!-- Provider Model: Title to see all the networks [CHAR LIMIT=50] -->
+ <string name="see_all_networks">See all</string>
+ <!-- Summary for warning to disconnect ethernet first then switch to other networks. [CHAR LIMIT=60] -->
+ <string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string>
+ <!-- Message to describe "Wi-Fi scan always available feature" when Wi-Fi is off and Wi-Fi scanning is on. [CHAR LIMIT=NONE] -->
+ <string name="wifi_scan_notify_message">To improve device experience, apps and services can still scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. You can change this in Wi\u2011Fi scanning settings. <annotation id="link">Change</annotation></string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 51eabf6..93d60cc 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -416,7 +416,9 @@
</style>
<!-- Overridden by values-television/styles.xml with tv-specific settings -->
- <style name="volume_dialog_theme" parent="Theme.SystemUI"/>
+ <style name="volume_dialog_theme" parent="Theme.SystemUI">
+ <item name="android:windowIsFloating">true</item>
+ </style>
<style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
@@ -903,4 +905,108 @@
<!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen. -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
</style>
+
+ <style name="Animation.InternetDialog" parent="@android:style/Animation.InputMethod">
+ </style>
+
+ <style name="Widget.SliceView.Panel">
+ <item name="titleSize">16sp</item>
+ <item name="rowStyle">@style/SliceRow</item>
+ <item name="android:background">?android:attr/colorBackgroundFloating</item>
+ </style>
+
+ <style name="SliceRow">
+ <!-- 2dp start padding for the start icon -->
+ <item name="titleItemStartPadding">2dp</item>
+ <item name="titleItemEndPadding">0dp</item>
+
+ <!-- Padding between content and the start icon is 14dp -->
+ <item name="contentStartPadding">14dp</item>
+ <!-- Padding between content and end items is 16dp -->
+ <item name="contentEndPadding">16dp</item>
+
+ <!-- Both side margins of end item are 16dp -->
+ <item name="endItemStartPadding">16dp</item>
+ <item name="endItemEndPadding">16dp</item>
+
+ <!-- Both side margins of bottom divider are 12dp -->
+ <item name="bottomDividerStartPadding">12dp</item>
+ <item name="bottomDividerEndPadding">12dp</item>
+
+ <item name="actionDividerHeight">32dp</item>
+ </style>
+
+ <style name="Theme.SystemUI.Dialog.Internet">
+ <item name="android:windowBackground">@drawable/internet_dialog_background</item>
+ </style>
+
+ <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item>
+ </style>
+
+ <style name="TrimmedHorizontalProgressBar"
+ parent="android:Widget.Material.ProgressBar.Horizontal">
+ <item name="android:indeterminateDrawable">
+ @drawable/progress_indeterminate_horizontal_material_trimmed
+ </item>
+ <item name="android:minHeight">4dp</item>
+ <item name="android:maxHeight">4dp</item>
+ </style>
+
+ <!-- Internet Dialog -->
+ <style name="InternetDialog">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center_vertical|start</item>
+ <item name="android:layout_marginStart">@dimen/internet_dialog_network_layout_margin</item>
+ </style>
+
+ <style name="InternetDialog.Network">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">88dp</item>
+ <item name="android:layout_marginEnd">@dimen/internet_dialog_network_layout_margin</item>
+ <item name="android:paddingStart">22dp</item>
+ <item name="android:paddingEnd">22dp</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:focusable">true</item>
+ <item name="android:clickable">true</item>
+ </style>
+
+ <style name="InternetDialog.NetworkTitle">
+ <item name="android:layout_marginEnd">7dp</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog</item>
+ </style>
+
+ <style name="InternetDialog.NetworkTitle.Active">
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Active</item>
+ </style>
+
+ <style name="InternetDialog.NetworkSummary">
+ <item name="android:layout_marginEnd">34dp</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary</item>
+ </style>
+
+ <style name="InternetDialog.NetworkSummary.Active">
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary.Active
+ </item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Secondary">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Active"/>
+
+ <style name="TextAppearance.InternetDialog.Secondary.Active"/>
+
</resources>
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index d6c6a60..c3510b6 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -44,23 +44,6 @@
/>
<Constraint
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:alpha="0.5"
- android:visibility="gone"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline"
- app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
- app:layout_constraintEnd_toEndOf="parent"
- />
-
- <Constraint
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size_small"
android:layout_height="@dimen/qs_media_album_size_small"
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index 0e284e6..6b83aae 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -45,22 +45,6 @@
android:layout_marginBottom="4dp" />
<Constraint
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="16dp"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:alpha="0.5"
- android:visibility="gone"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
- app:layout_constraintEnd_toEndOf="parent"
- />
-
- <Constraint
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size"
android:layout_height="@dimen/qs_media_album_size"
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
index 5c41ad8..b6258d1 100644
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
@@ -19,25 +19,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_icon_size"
- android:layout_height="@dimen/qs_media_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
- android:id="@+id/recommendation_card_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
android:id="@+id/media_cover1_container"
android:layout_width="0dp"
android:layout_height="@dimen/qs_aa_media_rec_album_size_collapsed"
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
index 8a3d5ca..2fb3341 100644
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
@@ -19,25 +19,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_icon_size"
- android:layout_height="@dimen/qs_media_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
- android:id="@+id/recommendation_card_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
android:id="@+id/media_cover1_container"
android:layout_width="0dp"
android:layout_height="@dimen/qs_aa_media_rec_album_size_expanded"
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 92f89d6..a383cab 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -20,12 +20,16 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.graphics.Color;
import android.icu.text.NumberFormat;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -67,20 +71,20 @@
BroadcastDispatcher broadcastDispatcher,
BatteryController batteryController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController bypassController) {
+ KeyguardBypassController bypassController,
+ @Main Resources resources
+ ) {
super(view);
mStatusBarStateController = statusBarStateController;
- mIsDozing = mStatusBarStateController.isDozing();
- mDozeAmount = mStatusBarStateController.getDozeAmount();
mBroadcastDispatcher = broadcastDispatcher;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mBypassController = bypassController;
mBatteryController = batteryController;
mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
- mBurmeseLineSpacing = getContext().getResources().getFloat(
+ mBurmeseLineSpacing = resources.getFloat(
R.dimen.keyguard_clock_line_spacing_scale_burmese);
- mDefaultLineSpacing = getContext().getResources().getFloat(
+ mDefaultLineSpacing = resources.getFloat(
R.dimen.keyguard_clock_line_spacing_scale);
}
@@ -106,7 +110,7 @@
}
};
- private final StatusBarStateController.StateListener mStatusBarStatePersistentListener =
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
@@ -144,11 +148,11 @@
mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
mDozeAmount = mStatusBarStateController.getDozeAmount();
+ mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
mBatteryController.addCallback(mBatteryCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
- mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
- mStatusBarStateController.addCallback(mStatusBarStatePersistentListener);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
refreshTime();
initColors();
@@ -160,9 +164,7 @@
mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mBatteryController.removeCallback(mBatteryCallback);
- if (!mView.isAttachedToWindow()) {
- mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
- }
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
/** Animate the clock appearance */
@@ -191,6 +193,14 @@
mView.refreshFormat();
}
+ /**
+ * Return locallly stored dozing state.
+ */
+ @VisibleForTesting
+ public boolean isDozing() {
+ return mIsDozing;
+ }
+
private void updateLocale() {
Locale currLocale = Locale.getDefault();
if (!Objects.equals(currLocale, mLocale)) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index f4a3fb2..7a01b4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -20,6 +20,7 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.WallpaperManager;
+import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
import android.widget.FrameLayout;
@@ -30,6 +31,7 @@
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -65,18 +67,22 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
+ private final Resources mResources;
/**
* Clock for both small and large sizes
*/
private AnimatableClockController mClockViewController;
- private FrameLayout mClockFrame;
+ private FrameLayout mClockFrame; // top aligned clock
private AnimatableClockController mLargeClockViewController;
- private FrameLayout mLargeClockFrame;
+ private FrameLayout mLargeClockFrame; // centered clock
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardBypassController mBypassController;
+ private int mLargeClockTopMargin = 0;
+ private int mKeyguardClockTopMargin = 0;
+
/**
* Listener for changes to the color palette.
*
@@ -113,7 +119,8 @@
KeyguardBypassController bypassController,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- SmartspaceTransitionController smartspaceTransitionController) {
+ SmartspaceTransitionController smartspaceTransitionController,
+ @Main Resources resources) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mColorExtractor = colorExtractor;
@@ -125,6 +132,7 @@
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mBypassController = bypassController;
mSmartspaceController = smartspaceController;
+ mResources = resources;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mSmartspaceTransitionController = smartspaceTransitionController;
@@ -154,7 +162,8 @@
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
- mBypassController);
+ mBypassController,
+ mResources);
mClockViewController.init();
mLargeClockViewController =
@@ -164,7 +173,8 @@
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
- mBypassController);
+ mBypassController,
+ mResources);
mLargeClockViewController.init();
}
@@ -175,6 +185,8 @@
}
mColorExtractor.addOnColorsChangedListener(mColorsListener);
mView.updateColors(getGradientColors());
+ mKeyguardClockTopMargin =
+ mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
View ksa = mView.findViewById(R.id.keyguard_status_area);
@@ -249,6 +261,8 @@
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
+ mKeyguardClockTopMargin =
+ mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
updateClockLayout();
}
@@ -257,9 +271,12 @@
if (mSmartspaceController.isEnabled()) {
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
MATCH_PARENT);
- lp.topMargin = getContext().getResources().getDimensionPixelSize(
+ mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize(
R.dimen.keyguard_large_clock_top_margin);
+ lp.topMargin = mLargeClockTopMargin;
mLargeClockFrame.setLayoutParams(lp);
+ } else {
+ mLargeClockTopMargin = 0;
}
}
@@ -369,6 +386,28 @@
}
}
+ /**
+ * Get y-bottom position of the currently visible clock on the keyguard.
+ * We can't directly getBottom() because clock changes positions in AOD for burn-in
+ */
+ int getClockBottom(int statusBarHeaderHeight) {
+ if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+ View clock = mLargeClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view_large);
+ int frameHeight = mLargeClockFrame.getHeight();
+ int clockHeight = clock.getHeight();
+ return frameHeight / 2 + clockHeight / 2;
+ } else {
+ return mClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view).getHeight()
+ + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ }
+ }
+
+ boolean isClockTopAligned() {
+ return mLargeClockFrame.getVisibility() != View.VISIBLE;
+ }
+
private void updateAodIcons() {
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 72e5028..6b3e9c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -185,6 +185,20 @@
}
/**
+ * Get y-bottom position of the currently visible clock.
+ */
+ public int getClockBottom(int statusBarHeaderHeight) {
+ return mKeyguardClockSwitchController.getClockBottom(statusBarHeaderHeight);
+ }
+
+ /**
+ * @return true if the currently displayed clock is top aligned (as opposed to center aligned)
+ */
+ public boolean isClockTopAligned() {
+ return mKeyguardClockSwitchController.isClockTopAligned();
+ }
+
+ /**
* Set whether the view accessibility importance mode.
*/
public void setStatusAccessibilityImportance(int mode) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 28a54d5..e115c34 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -132,8 +132,7 @@
.alpha(1f)
.withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
.start();
- } else if (mUnlockedScreenOffAnimationController
- .isScreenOffLightRevealAnimationPlaying()) {
+ } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) {
mKeyguardViewVisibilityAnimating = true;
// Ask the screen off animation controller to animate the keyguard visibility for us
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 622419a8..ef4353b 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -22,12 +22,12 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.view.Gravity;
import android.view.View;
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;
@@ -45,9 +45,10 @@
private int mRadius;
private ImageView mLockIcon;
- private ImageView mUnlockBgView;
+ private ImageView mBgView;
private int mLockIconColor;
+ private boolean mUseBackground = false;
public LockIconView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -58,19 +59,19 @@
public void onFinishInflate() {
super.onFinishInflate();
mLockIcon = findViewById(R.id.lock_icon);
- mUnlockBgView = findViewById(R.id.lock_icon_bg);
+ mBgView = findViewById(R.id.lock_icon_bg);
}
- void updateColorAndBackgroundVisibility(boolean useBackground) {
- if (useBackground) {
+ void updateColorAndBackgroundVisibility() {
+ if (mUseBackground && mLockIcon.getDrawable() != null) {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
- mUnlockBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
- mUnlockBgView.setVisibility(View.VISIBLE);
+ mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
+ mBgView.setVisibility(View.VISIBLE);
} else {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
R.attr.wallpaperTextColorAccent);
- mUnlockBgView.setVisibility(View.GONE);
+ mBgView.setVisibility(View.GONE);
}
mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
@@ -78,9 +79,29 @@
void setImageDrawable(Drawable drawable) {
mLockIcon.setImageDrawable(drawable);
+
+ if (!mUseBackground) return;
+
+ if (drawable == null) {
+ mBgView.setVisibility(View.INVISIBLE);
+ } else {
+ mBgView.setVisibility(View.VISIBLE);
+ }
}
- void setCenterLocation(@NonNull PointF center, int radius) {
+ /**
+ * Whether or not to render the lock icon background. Mainly used for UDPFS.
+ */
+ public void setUseBackground(boolean useBackground) {
+ mUseBackground = useBackground;
+ updateColorAndBackgroundVisibility();
+ }
+
+ /**
+ * Set the location of the lock icon.
+ */
+ @VisibleForTesting
+ public void setCenterLocation(@NonNull PointF center, int radius) {
mLockIconCenter = center;
mRadius = radius;
@@ -91,13 +112,11 @@
mLockIconCenter.x + mRadius,
mLockIconCenter.y + mRadius);
- setX(mSensorRect.left);
- setY(mSensorRect.top);
-
- final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- (int) (mSensorRect.right - mSensorRect.left),
- (int) (mSensorRect.bottom - mSensorRect.top));
- lp.gravity = Gravity.CENTER;
+ final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ lp.width = (int) (mSensorRect.right - mSensorRect.left);
+ lp.height = (int) (mSensorRect.bottom - mSensorRect.top);
+ lp.topMargin = (int) mSensorRect.top;
+ lp.setMarginStart((int) mSensorRect.left);
setLayoutParams(lp);
}
@@ -114,5 +133,6 @@
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
pw.println("Radius in pixels: " + mRadius);
+ pw.println("topLeft= (" + getX() + ", " + getY() + ")");
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 509ac8a..52ebf2f 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -19,6 +19,8 @@
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
import android.content.Context;
import android.content.res.Configuration;
@@ -32,6 +34,7 @@
import android.os.Process;
import android.os.Vibrator;
import android.util.DisplayMetrics;
+import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
@@ -46,6 +49,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -58,6 +62,8 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.airbnb.lottie.LottieAnimationView;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
@@ -67,7 +73,8 @@
/**
* Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
*
- * This view will only be shown if the user has UDFPS or FaceAuth enrolled
+ * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock
+ * icon will show a set distance from the bottom of the device.
*/
@StatusBarComponent.StatusBarScope
public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
@@ -92,6 +99,8 @@
@NonNull private final DelayableExecutor mExecutor;
private boolean mUdfpsEnrolled;
+ @NonNull private LottieAnimationView mAodFp;
+
@NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
@NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
@NonNull private final Drawable mLockIcon;
@@ -99,6 +108,7 @@
@NonNull private CharSequence mUnlockedLabel;
@NonNull private CharSequence mLockedLabel;
@Nullable private final Vibrator mVibrator;
+ @Nullable private final AuthRippleController mAuthRippleController;
private boolean mIsDozing;
private boolean mIsBouncerShowing;
@@ -109,6 +119,7 @@
private boolean mIsKeyguardShowing;
private boolean mUserUnlockedWithBiometric;
private Runnable mCancelDelayedUpdateVisibilityRunnable;
+ private Runnable mOnGestureDetectedRunnable;
private boolean mUdfpsSupported;
private float mHeightPixels;
@@ -118,6 +129,12 @@
private boolean mShowUnlockIcon;
private boolean mShowLockIcon;
+ // for udfps when strong auth is required or unlocked on AOD
+ private boolean mShowAODFpIcon;
+ private final int mMaxBurnInOffsetX;
+ private final int mMaxBurnInOffsetY;
+ private float mInterpolatedDarkAmount;
+
private boolean mDownDetected;
private boolean mDetectedLongPress;
private final Rect mSensorTouchLocation = new Rect();
@@ -135,7 +152,8 @@
@NonNull AccessibilityManager accessibilityManager,
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
- @Nullable Vibrator vibrator
+ @Nullable Vibrator vibrator,
+ @Nullable AuthRippleController authRippleController
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -148,8 +166,15 @@
mConfigurationController = configurationController;
mExecutor = executor;
mVibrator = vibrator;
+ mAuthRippleController = authRippleController;
final Context context = view.getContext();
+ mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+ mMaxBurnInOffsetX = context.getResources()
+ .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = context.getResources()
+ .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+
mUnlockIcon = mView.getContext().getResources().getDrawable(
R.drawable.ic_unlock,
mView.getContext().getTheme());
@@ -173,15 +198,14 @@
@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;
-
+ updateIsUdfpsEnrolled();
updateConfiguration();
updateKeyguardShowing();
mUserUnlockedWithBiometric = false;
+
mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
mIsDozing = mStatusBarStateController.isDozing();
+ mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount();
mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
mStatusBarState = mStatusBarStateController.getState();
@@ -189,15 +213,18 @@
updateColors();
mConfigurationController.addCallback(mConfigurationListener);
+ mAuthController.addCallback(mAuthControllerCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
mDownDetected = false;
+ updateBurnInOffsets();
updateVisibility();
}
@Override
protected void onViewDetached() {
+ mAuthController.removeCallback(mAuthControllerCallback);
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
@@ -227,7 +254,7 @@
mCancelDelayedUpdateVisibilityRunnable = null;
}
- if (!mIsKeyguardShowing) {
+ if (!mIsKeyguardShowing && !mIsDozing) {
mView.setVisibility(View.INVISIBLE);
return;
}
@@ -238,6 +265,7 @@
mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
&& (!mUdfpsEnrolled || !mRunningFPS);
mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
+ mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
final CharSequence prevContentDescription = mView.getContentDescription();
if (mShowLockIcon) {
@@ -260,10 +288,22 @@
}
mView.setVisibility(View.VISIBLE);
mView.setContentDescription(mUnlockedLabel);
+ } else if (mShowAODFpIcon) {
+ mView.setImageDrawable(null);
+ mView.setContentDescription(null);
+ mAodFp.setVisibility(View.VISIBLE);
+ mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel);
+ mView.setVisibility(View.VISIBLE);
} else {
mView.setVisibility(View.INVISIBLE);
mView.setContentDescription(null);
}
+
+ if (!mShowAODFpIcon) {
+ mAodFp.setVisibility(View.INVISIBLE);
+ mAodFp.setContentDescription(null);
+ }
+
if (!Objects.equals(prevContentDescription, mView.getContentDescription())
&& mView.getContentDescription() != null) {
mView.announceForAccessibility(mView.getContentDescription());
@@ -305,7 +345,7 @@
}
private void updateColors() {
- mView.updateColorAndBackgroundVisibility(mUdfpsSupported);
+ mView.updateColorAndBackgroundVisibility();
}
private void updateConfiguration() {
@@ -340,10 +380,12 @@
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mUdfpsSupported: " + mUdfpsSupported);
pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
+ pw.println(" mShowAODFpIcon: " + mShowAODFpIcon);
pw.println(" mIsDozing: " + mIsDozing);
pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
@@ -351,17 +393,59 @@
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState));
pw.println(" mQsExpanded: " + mQsExpanded);
+ pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
if (mView != null) {
mView.dump(fd, pw, args);
}
}
+ /** Every minute, update the aod icon's burn in offset */
+ public void dozeTimeTick() {
+ updateBurnInOffsets();
+ }
+
+ private void updateBurnInOffsets() {
+ float offsetX = MathUtils.lerp(0f,
+ getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
+ - mMaxBurnInOffsetX, mInterpolatedDarkAmount);
+ float offsetY = MathUtils.lerp(0f,
+ getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
+ - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
+ float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
+
+ mAodFp.setTranslationX(offsetX);
+ mAodFp.setTranslationY(offsetY);
+ mAodFp.setProgress(progress);
+ mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ }
+
+ private void updateIsUdfpsEnrolled() {
+ boolean wasUdfpsSupported = mUdfpsSupported;
+ boolean wasUdfpsEnrolled = mUdfpsEnrolled;
+
+ mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+ mView.setUseBackground(mUdfpsSupported);
+
+ mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
+ updateVisibility();
+ }
+ }
+
private StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ mInterpolatedDarkAmount = eased;
+ updateBurnInOffsets();
+ }
+
+ @Override
public void onDozingChanged(boolean isDozing) {
mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateIsUdfpsEnrolled();
updateVisibility();
}
@@ -435,7 +519,7 @@
mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
KeyguardUpdateMonitor.getCurrentUser());
}
- mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ updateIsUdfpsEnrolled();
updateVisibility();
}
@@ -481,8 +565,7 @@
// intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or
// MotionEvent.ACTION_UP (see #onTouchEvent)
- mDownDetected = true;
- if (mVibrator != null) {
+ if (mVibrator != null && !mDownDetected) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
@@ -490,6 +573,8 @@
"lockIcon-onDown",
VIBRATION_SONIFICATION_ATTRIBUTES);
}
+
+ mDownDetected = true;
return true;
}
@@ -497,8 +582,10 @@
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 +593,6 @@
"lockIcon-onLongPress",
VIBRATION_SONIFICATION_ATTRIBUTES);
}
- mDetectedLongPress = true;
- onAffordanceClick();
}
public boolean onSingleTapUp(MotionEvent e) {
@@ -531,15 +616,27 @@
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();
+ if (mOnGestureDetectedRunnable != null) {
+ mOnGestureDetectedRunnable.run();
+ }
mKeyguardViewController.showBouncer(/* scrim */ true);
+ return true;
}
});
@@ -548,16 +645,18 @@
* in a 'clickable' state
* @return whether to intercept the touch event
*/
- public boolean onTouchEvent(MotionEvent event) {
+ public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
- && mView.getVisibility() == View.VISIBLE) {
+ && (mView.getVisibility() == View.VISIBLE
+ || mAodFp.getVisibility() == View.VISIBLE)) {
+ mOnGestureDetectedRunnable = onGestureDetectedRunnable;
mGestureDetector.onTouchEvent(event);
}
// we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP
// or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV
// after the lock icon disappears on device entry
- if (mDownDetected && mDetectedLongPress) {
+ if (mDownDetected) {
if (event.getAction() == MotionEvent.ACTION_CANCEL
|| event.getAction() == MotionEvent.ACTION_UP) {
mDownDetected = false;
@@ -577,4 +676,12 @@
public void setAlpha(float alpha) {
mView.setAlpha(alpha);
}
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onAllAuthenticatorsRegistered() {
+ updateIsUdfpsEnrolled();
+ updateConfiguration();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
index 3a0357d..4331f52 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
@@ -16,7 +16,8 @@
package com.android.keyguard.dagger;
-import com.android.systemui.statusbar.phone.UserAvatarView;
+import android.widget.FrameLayout;
+
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import dagger.BindsInstance;
@@ -31,8 +32,7 @@
/** Simple factory for {@link KeyguardUserSwitcherComponent}. */
@Subcomponent.Factory
interface Factory {
- KeyguardQsUserSwitchComponent build(
- @BindsInstance UserAvatarView userAvatarView);
+ KeyguardQsUserSwitchComponent build(@BindsInstance FrameLayout userAvatarContainer);
}
/** Builds a {@link KeyguardQsUserSwitchController}. */
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 76f30a8..00b33a4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -65,6 +65,7 @@
import com.android.systemui.power.PowerUI;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.screenrecord.RecordingController;
@@ -364,6 +365,7 @@
@Inject Lazy<UiEventLogger> mUiEventLogger;
@Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
@Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
+ @Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
@Inject
public Dependency() {
@@ -578,6 +580,7 @@
mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
mProviders.put(EdgeBackGestureHandler.Factory.class,
mEdgeBackGestureHandlerFactoryLazy::get);
+ mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get);
mProviders.put(UiEventLogger.class, mUiEventLogger::get);
mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index a68f796..8379ccf 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -20,6 +20,8 @@
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -29,7 +31,6 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
-import android.view.DisplayInfo;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -90,7 +91,7 @@
mMiniBitmap = null;
}
- class GLEngine extends Engine {
+ class GLEngine extends Engine implements DisplayListener {
// Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
// set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
@VisibleForTesting
@@ -102,15 +103,15 @@
private EglHelper mEglHelper;
private final Runnable mFinishRenderingTask = this::finishRendering;
private boolean mNeedRedraw;
- private int mWidth = 1;
- private int mHeight = 1;
+
+ private boolean mDisplaySizeValid = false;
+ private int mDisplayWidth = 1;
+ private int mDisplayHeight = 1;
+
private int mImgWidth = 1;
private int mImgHeight = 1;
- private float mPageWidth = 1.f;
- private float mPageOffset = 1.f;
- GLEngine() {
- }
+ GLEngine() { }
@VisibleForTesting
GLEngine(Handler handler) {
@@ -119,18 +120,29 @@
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
+ Trace.beginSection("ImageWallpaper.Engine#onCreate");
mEglHelper = getEglHelperInstance();
// Deferred init renderer because we need to get wallpaper by display context.
mRenderer = getRendererInstance();
setFixedSizeAllowed(true);
updateSurfaceSize();
- Rect window = getDisplayContext()
- .getSystemService(WindowManager.class)
- .getCurrentWindowMetrics()
- .getBounds();
- mHeight = window.height();
- mWidth = window.width();
mRenderer.setOnBitmapChanged(this::updateMiniBitmap);
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, mWorker.getThreadHandler());
+ Trace.endSection();
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) { }
+
+ @Override
+ public void onDisplayRemoved(int displayId) { }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == getDisplayContext().getDisplayId()) {
+ mDisplaySizeValid = false;
+ }
}
EglHelper getEglHelperInstance() {
@@ -154,26 +166,10 @@
if (pages == mPages) return;
mPages = pages;
if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
- updateShift();
mWorker.getThreadHandler().post(() ->
computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
}
- private void updateShift() {
- if (mImgHeight == 0) {
- mPageOffset = 0;
- mPageWidth = 1;
- return;
- }
- // calculate shift
- DisplayInfo displayInfo = new DisplayInfo();
- getDisplayContext().getDisplay().getDisplayInfo(displayInfo);
- int screenWidth = displayInfo.getNaturalWidth();
- float imgWidth = Math.min(mImgWidth > 0 ? screenWidth / (float) mImgWidth : 1.f, 1.f);
- mPageWidth = imgWidth;
- mPageOffset = (1 - imgWidth) / (float) (mPages - 1);
- }
-
private void updateMiniBitmap(Bitmap b) {
if (b == null) return;
int size = Math.min(b.getWidth(), b.getHeight());
@@ -203,13 +199,22 @@
}
@Override
+ public boolean shouldWaitForEngineShown() {
+ return true;
+ }
+
+ @Override
public void onDestroy() {
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(this);
mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
+ Trace.beginSection("ImageWallpaper.Engine#onDestroy");
mRenderer.finish();
mRenderer = null;
mEglHelper.finish();
mEglHelper = null;
+ Trace.endSection();
});
}
@@ -268,6 +273,16 @@
* (1-Wr)].
*/
private RectF pageToImgRect(RectF area) {
+ if (!mDisplaySizeValid) {
+ Rect window = getDisplayContext()
+ .getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getBounds();
+ mDisplayWidth = window.width();
+ mDisplayHeight = window.height();
+ mDisplaySizeValid = true;
+ }
+
// Width of a page for the caller of this API.
float virtualPageWidth = 1f / (float) mPages;
float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
@@ -275,12 +290,24 @@
int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
RectF imgArea = new RectF();
+
+ if (mImgWidth == 0 || mImgHeight == 0 || mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+ return imgArea;
+ }
+
imgArea.bottom = area.bottom;
imgArea.top = area.top;
+
+ float imageScale = Math.min(((float) mImgHeight) / mDisplayHeight, 1);
+ float mappedScreenWidth = mDisplayWidth * imageScale;
+ float pageWidth = Math.min(1.0f,
+ mImgWidth > 0 ? mappedScreenWidth / (float) mImgWidth : 1.f);
+ float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
imgArea.left = MathUtils.constrain(
- leftPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1);
+ leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
imgArea.right = MathUtils.constrain(
- rightPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1);
+ rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
if (imgArea.left > imgArea.right) {
// take full page
imgArea.left = 0;
@@ -293,7 +320,6 @@
private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
Bitmap b) {
List<WallpaperColors> colors = new ArrayList<>(areas.size());
- updateShift();
for (int i = 0; i < areas.size(); i++) {
RectF area = pageToImgRect(areas.get(i));
if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
@@ -322,8 +348,10 @@
public void onSurfaceCreated(SurfaceHolder holder) {
if (mWorker == null) return;
mWorker.getThreadHandler().post(() -> {
+ Trace.beginSection("ImageWallpaper#onSurfaceCreated");
mEglHelper.init(holder, needSupportWideColorGamut());
mRenderer.onSurfaceCreated();
+ Trace.endSection();
});
}
@@ -340,9 +368,11 @@
}
private void drawFrame() {
+ Trace.beginSection("ImageWallpaper#drawFrame");
preRender();
requestRender();
postRender();
+ Trace.endSection();
}
public void preRender() {
@@ -409,6 +439,7 @@
// This method should only be invoked from worker thread.
Trace.beginSection("ImageWallpaper#postRender");
scheduleFinishRendering();
+ reportEngineShown(false /* waitForEngineShown */);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index affad7a..8c63f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -438,6 +438,12 @@
private boolean mCancelled;
@Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mCallback.onBeginDrag(animView);
+ }
+
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java
index d8e80fe..0d7551f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java
@@ -30,7 +30,7 @@
/**
* A span that turns the text wrapped by annotation tag into the clickable link text.
*/
-class AnnotationLinkSpan extends ClickableSpan {
+public class AnnotationLinkSpan extends ClickableSpan {
private final Optional<View.OnClickListener> mClickListener;
private AnnotationLinkSpan(View.OnClickListener listener) {
@@ -50,7 +50,7 @@
* @param linkInfos used to attach the click action into the corresponding span
* @return the text attached with the span
*/
- static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) {
+ public static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) {
final SpannableString msg = new SpannableString(text);
final Annotation[] spans =
msg.getSpans(/* queryStart= */ 0, msg.length(), Annotation.class);
@@ -78,12 +78,12 @@
/**
* Data class to store the annotation and the click action.
*/
- static class LinkInfo {
- static final String DEFAULT_ANNOTATION = "link";
+ public static class LinkInfo {
+ public static final String DEFAULT_ANNOTATION = "link";
private final Optional<String> mAnnotation;
private final Optional<View.OnClickListener> mListener;
- LinkInfo(@NonNull String annotation, View.OnClickListener listener) {
+ public LinkInfo(@NonNull String annotation, View.OnClickListener listener) {
mAnnotation = Optional.of(annotation);
mListener = Optional.ofNullable(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
index 81a13a2..4082015 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
@@ -57,7 +57,9 @@
public void run() {
mView.removeCallbacks(this);
mView.show(false /* show */, true /* animate */, () -> {
- mWindowManager.removeView(mView);
+ if (mView.isAttachedToWindow()) {
+ mWindowManager.removeView(mView);
+ }
});
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index c9e6771..5616a00 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -40,10 +40,10 @@
* After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
* indicating that restoring is finished for a given user.
*/
-class BackupHelper : BackupAgentHelper() {
+open class BackupHelper : BackupAgentHelper() {
companion object {
- private const val TAG = "BackupHelper"
+ const val TAG = "BackupHelper"
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 3f61d3c..fd37b35 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -126,6 +126,7 @@
boolean mCredentialAllowed;
boolean mSkipIntro;
long mOperationId;
+ long mRequestId;
@BiometricMultiSensorMode int mMultiSensorConfig;
}
@@ -172,6 +173,12 @@
return this;
}
+ /** Unique id for this request. */
+ public Builder setRequestId(long requestId) {
+ mConfig.mRequestId = requestId;
+ return this;
+ }
+
/** The multi-sensor mode. */
public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
mConfig.mMultiSensorConfig = multiSensorConfig;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0ce1846..0790af9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -42,6 +42,7 @@
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
@@ -49,6 +50,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.WindowManager;
@@ -76,6 +78,9 @@
/**
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
* appropriate biometric UI (e.g. BiometricDialogView).
+ *
+ * Also coordinates biometric-related things, such as UDFPS, with
+ * {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@@ -115,6 +120,8 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
+ @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+
private class BiometricTaskStackListener extends TaskStackListener {
@Override
public void onTaskStackChanged() {
@@ -122,6 +129,21 @@
}
}
+ private final FingerprintStateListener mFingerprintStateListener =
+ new FingerprintStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ Log.d(TAG, "onEnrollmentsChanged, userId: " + userId
+ + ", sensorId: " + sensorId
+ + ", hasEnrollments: " + hasEnrollments);
+ for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) {
+ if (prop.sensorId == sensorId) {
+ mUdfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
+ };
+
@NonNull
private final IFingerprintAuthenticatorsRegisteredCallback
mFingerprintAuthenticatorsRegisteredCallback =
@@ -436,6 +458,7 @@
mUdfpsControllerFactory = udfpsControllerFactory;
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
+ mUdfpsEnrolledForUser = new SparseBooleanArray();
mOrientationListener = new BiometricOrientationEventListener(context,
() -> {
onOrientationChanged();
@@ -474,6 +497,7 @@
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
mFingerprintAuthenticatorsRegisteredCallback);
+ mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener);
}
mTaskStackListener = new BiometricTaskStackListener();
@@ -501,7 +525,7 @@
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
@Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
@@ -515,6 +539,7 @@
+ ", credentialAllowed: " + credentialAllowed
+ ", requireConfirmation: " + requireConfirmation
+ ", operationId: " + operationId
+ + ", requestId: " + requestId
+ ", multiSensorConfig: " + multiSensorConfig);
}
SomeArgs args = SomeArgs.obtain();
@@ -526,6 +551,7 @@
args.argi1 = userId;
args.arg6 = opPackageName;
args.arg7 = operationId;
+ args.arg8 = requestId;
args.argi2 = multiSensorConfig;
boolean skipAnimation = false;
@@ -629,6 +655,7 @@
if (mCurrentDialog == null) {
// Could be possible if the caller canceled authentication after credential success
// but before the client was notified.
+ if (DEBUG) Log.d(TAG, "dialog already gone");
return;
}
@@ -670,7 +697,7 @@
return false;
}
- return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps);
+ return mUdfpsEnrolledForUser.get(userId);
}
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
@@ -683,6 +710,7 @@
final int userId = args.argi1;
final String opPackageName = (String) args.arg6;
final long operationId = (long) args.arg7;
+ final long requestId = (long) args.arg8;
final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
// Create a new dialog but do not replace the current one yet.
@@ -695,6 +723,7 @@
opPackageName,
skipAnimation,
operationId,
+ requestId,
multiSensorConfig);
if (newDialog == null) {
@@ -772,7 +801,7 @@
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
- boolean skipIntro, long operationId,
+ boolean skipIntro, long operationId, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
@@ -782,11 +811,16 @@
.setOpPackageName(opPackageName)
.setSkipIntro(skipIntro)
.setOperationId(operationId)
+ .setRequestId(requestId)
.setMultiSensorConfig(multiSensorConfig)
.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/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 1df8ad5..2630f119 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -16,14 +16,19 @@
package com.android.systemui.biometrics
+import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.graphics.PointF
import android.hardware.biometrics.BiometricSourceType
+import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -34,9 +39,14 @@
import com.android.systemui.statusbar.phone.StatusBar
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.ViewController
import java.io.PrintWriter
import javax.inject.Inject
+import javax.inject.Provider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+
+private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L
/***
* Controls the ripple effect that shows when authentication is successful.
@@ -49,37 +59,65 @@
private val authController: AuthController,
private val configurationController: ConfigurationController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val keyguardStateController: KeyguardStateController,
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
private val commandRegistry: CommandRegistry,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val bypassController: KeyguardBypassController,
private val biometricUnlockController: BiometricUnlockController,
+ private val udfpsControllerProvider: Provider<UdfpsController>,
+ private val statusBarStateController: StatusBarStateController,
rippleView: AuthRippleView?
-) : ViewController<AuthRippleView>(rippleView) {
+) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
+ WakefulnessLifecycle.Observer {
+
+ @VisibleForTesting
+ internal var startLightRevealScrimOnKeyguardFadingAway = false
var fingerprintSensorLocation: PointF? = null
private var faceSensorLocation: PointF? = null
private var circleReveal: LightRevealEffect? = null
+ private var udfpsController: UdfpsController? = null
+
+ private var dwellScale = 2f
+ private var expandedDwellScale = 2.5f
+ private var aodDwellScale = 1.9f
+ private var aodExpandedDwellScale = 2.3f
+ private var udfpsRadius: Float = -1f
+
+ override fun onInit() {
+ mView.setAlphaInDuration(sysuiContext.resources.getInteger(
+ R.integer.auth_ripple_alpha_in_duration).toLong())
+ }
+
@VisibleForTesting
public override fun onViewAttached() {
+ authController.addCallback(authControllerCallback)
updateRippleColor()
updateSensorLocation()
- authController.addCallback(authControllerCallback)
+ updateUdfpsDependentParams()
+ udfpsController?.addCallback(udfpsControllerCallback)
configurationController.addCallback(configurationChangedListener)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ keyguardStateController.addCallback(this)
+ wakefulnessLifecycle.addObserver(this)
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
}
@VisibleForTesting
public override fun onViewDetached() {
+ udfpsController?.removeCallback(udfpsControllerCallback)
authController.removeCallback(authControllerCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
configurationController.removeCallback(configurationChangedListener)
+ keyguardStateController.removeCallback(this)
+ wakefulnessLifecycle.removeObserver(this)
commandRegistry.unregisterCommand("auth-ripple")
notificationShadeWindowController.setForcePluginOpen(false, this)
}
- private fun showRipple(biometricSourceType: BiometricSourceType?) {
+ fun showRipple(biometricSourceType: BiometricSourceType?) {
if (!keyguardUpdateMonitor.isKeyguardVisible ||
keyguardUpdateMonitor.userNeedsStrongAuth()) {
return
@@ -88,43 +126,61 @@
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
fingerprintSensorLocation != null) {
mView.setSensorLocation(fingerprintSensorLocation!!)
- showRipple()
+ showUnlockedRipple()
} else if (biometricSourceType == BiometricSourceType.FACE &&
faceSensorLocation != null) {
if (!bypassController.canBypass()) {
return
}
mView.setSensorLocation(faceSensorLocation!!)
- showRipple()
+ showUnlockedRipple()
}
}
- private fun showRipple() {
+ private fun showUnlockedRipple() {
notificationShadeWindowController.setForcePluginOpen(true, this)
- val biometricUnlockMode = biometricUnlockController.mode
- val useCircleReveal = circleReveal != null &&
- (biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK ||
- biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING ||
- biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock
val lightRevealScrim = statusBar.lightRevealScrim
if (useCircleReveal) {
lightRevealScrim?.revealEffect = circleReveal!!
+ startLightRevealScrimOnKeyguardFadingAway = true
}
- mView.startRipple(
+ mView.startUnlockedRipple(
/* end runnable */
Runnable {
notificationShadeWindowController.setForcePluginOpen(false, this)
- },
- /* circleReveal */
- if (useCircleReveal) {
- lightRevealScrim
- } else {
- null
}
)
}
+ override fun onKeyguardFadingAwayChanged() {
+ if (keyguardStateController.isKeyguardFadingAway) {
+ val lightRevealScrim = statusBar.lightRevealScrim
+ if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
+ val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ duration = RIPPLE_ANIMATION_DURATION
+ startDelay = keyguardStateController.keyguardFadingAwayDelay
+ addUpdateListener { animator ->
+ if (lightRevealScrim.revealEffect != circleReveal) {
+ // if the something else took over the reveal, let's do nothing.
+ return@addUpdateListener
+ }
+ lightRevealScrim.revealAmount = animator.animatedValue as Float
+ }
+ }
+ revealAnimator.start()
+ startLightRevealScrimOnKeyguardFadingAway = false
+ }
+ }
+ }
+
+ override fun onStartedGoingToSleep() {
+ // reset the light reveal start in case we were pending an unlock
+ startLightRevealScrimOnKeyguardFadingAway = false
+ }
+
fun updateSensorLocation() {
fingerprintSensorLocation = authController.fingerprintSensorLocation
faceSensorLocation = authController.faceAuthSensorLocation
@@ -146,7 +202,23 @@
Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
}
- val keyguardUpdateMonitorCallback =
+ private fun showDwellRipple() {
+ if (statusBarStateController.isDozing) {
+ mView.startDwellRipple(
+ /* startRadius */ udfpsRadius,
+ /* endRadius */ udfpsRadius * aodDwellScale,
+ /* expandedRadius */ udfpsRadius * aodExpandedDwellScale,
+ /* isDozing */ true)
+ } else {
+ mView.startDwellRipple(
+ /* startRadius */ udfpsRadius,
+ /* endRadius */ udfpsRadius * dwellScale,
+ /* expandedRadius */ udfpsRadius * expandedDwellScale,
+ /* isDozing */ false)
+ }
+ }
+
+ private val keyguardUpdateMonitorCallback =
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthenticated(
userId: Int,
@@ -155,9 +227,13 @@
) {
showRipple(biometricSourceType)
}
+
+ override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+ mView.retractRipple()
+ }
}
- val configurationChangedListener =
+ private val configurationChangedListener =
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
updateSensorLocation()
@@ -173,14 +249,70 @@
}
}
- private val authControllerCallback = AuthController.Callback { updateSensorLocation() }
+ private val udfpsControllerCallback =
+ object : UdfpsController.Callback {
+ override fun onFingerDown() {
+ if (fingerprintSensorLocation == null) {
+ Log.e("AuthRipple", "fingerprintSensorLocation=null onFingerDown. " +
+ "Skip showing dwell ripple")
+ return
+ }
+
+ mView.setSensorLocation(fingerprintSensorLocation!!)
+ showDwellRipple()
+ }
+
+ override fun onFingerUp() {
+ mView.retractRipple()
+ }
+ }
+
+ private val authControllerCallback = AuthController.Callback {
+ updateSensorLocation()
+ updateUdfpsDependentParams()
+ }
+
+ private fun updateUdfpsDependentParams() {
+ authController.udfpsProps?.let {
+ if (it.size > 0) {
+ udfpsRadius = it[0].sensorRadius.toFloat()
+ udfpsController = udfpsControllerProvider.get()
+
+ if (mView.isAttachedToWindow) {
+ udfpsController?.addCallback(udfpsControllerCallback)
+ }
+ }
+ }
+ }
inner class AuthRippleCommand : Command {
+ fun printLockScreenDwellInfo(pw: PrintWriter) {
+ pw.println("lock screen dwell ripple: " +
+ "\n\tsensorLocation=$fingerprintSensorLocation" +
+ "\n\tdwellScale=$dwellScale" +
+ "\n\tdwellExpand=$expandedDwellScale")
+ }
+
+ fun printAodDwellInfo(pw: PrintWriter) {
+ pw.println("aod dwell ripple: " +
+ "\n\tsensorLocation=$fingerprintSensorLocation" +
+ "\n\tdwellScale=$aodDwellScale" +
+ "\n\tdwellExpand=$aodExpandedDwellScale")
+ }
+
override fun execute(pw: PrintWriter, args: List<String>) {
if (args.isEmpty()) {
invalidCommand(pw)
} else {
when (args[0]) {
+ "dwell" -> {
+ showDwellRipple()
+ if (statusBarStateController.isDozing) {
+ printAodDwellInfo(pw)
+ } else {
+ printLockScreenDwellInfo(pw)
+ }
+ }
"fingerprint" -> {
pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
showRipple(BiometricSourceType.FINGERPRINT)
@@ -199,7 +331,7 @@
pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " +
args[2].toFloat())
mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat()))
- showRipple()
+ showUnlockedRipple()
}
else -> invalidCommand(pw)
}
@@ -209,6 +341,7 @@
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar auth-ripple <command>")
pw.println("Available commands:")
+ pw.println(" dwell")
pw.println(" fingerprint")
pw.println(" face")
pw.println(" custom <x-location: int> <y-location: int>")
@@ -219,4 +352,8 @@
help(pw)
}
}
+
+ companion object {
+ const val RIPPLE_ANIMATION_DURATION: Long = 1533
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 95ea8100..c6d26ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -27,21 +27,40 @@
import android.view.View
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
-import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.charging.RippleShader
-private const val RIPPLE_ANIMATION_DURATION: Long = 1533
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
/**
- * Expanding ripple effect on the transition from biometric authentication success to showing
+ * Expanding ripple effect
+ * - startUnlockedRipple for the transition from biometric authentication success to showing
* launcher.
+ * - startDwellRipple for the ripple expansion out when the user has their finger down on the UDFPS
+ * sensor area
+ * - retractRipple for the ripple animation inwards to signal a failure
*/
class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- private var rippleInProgress: Boolean = false
+ private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f)
+
+ private val dwellPulseDuration = 50L
+ private val dwellAlphaDuration = dwellPulseDuration
+ private val dwellAlpha: Float = 1f
+ private val dwellExpandDuration = 1200L - dwellPulseDuration
+
+ private val aodDwellPulseDuration = 50L
+ private var aodDwellAlphaDuration = aodDwellPulseDuration
+ private var aodDwellAlpha: Float = .8f
+ private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration
+
+ private val retractDuration = 400L
+ private var alphaInDuration: Long = 0
+ private var unlockedRippleInProgress: Boolean = false
private val rippleShader = RippleShader()
private val ripplePaint = Paint()
- private var radius: Float = 0.0f
+ private var retractAnimator: Animator? = null
+ private var dwellPulseOutAnimator: Animator? = null
+ private var radius: Float = 0f
set(value) {
rippleShader.radius = value
field = value
@@ -62,51 +81,200 @@
fun setSensorLocation(location: PointF) {
origin = location
- radius = maxOf(location.x, location.y, width - location.x, height - location.y)
- .toFloat()
+ radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
}
- fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) {
- if (rippleInProgress) {
- return // Ignore if ripple effect is already playing
+ fun setAlphaInDuration(duration: Long) {
+ alphaInDuration = duration
+ }
+
+ /**
+ * Animate ripple inwards back to radius 0
+ */
+ fun retractRipple() {
+ if (retractAnimator?.isRunning == true) {
+ return // let the animation finish
}
- val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
- interpolator = PathInterpolator(0.4f, 0f, 0f, 1f)
- duration = RIPPLE_ANIMATION_DURATION
+ if (dwellPulseOutAnimator?.isRunning == true) {
+ val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f)
+ .apply {
+ interpolator = retractInterpolator
+ duration = retractDuration
+ addUpdateListener { animator ->
+ val now = animator.currentPlayTime
+ rippleShader.progress = animator.animatedValue as Float
+ rippleShader.time = now.toFloat()
+
+ invalidate()
+ }
+ }
+
+ val retractAlphaAnimator = ValueAnimator.ofInt(255, 0).apply {
+ interpolator = Interpolators.LINEAR
+ duration = retractDuration
+ addUpdateListener { animator ->
+ rippleShader.color = ColorUtils.setAlphaComponent(
+ rippleShader.color,
+ animator.animatedValue as Int
+ )
+ invalidate()
+ }
+ }
+
+ retractAnimator = AnimatorSet().apply {
+ playTogether(retractRippleAnimator, retractAlphaAnimator)
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?) {
+ dwellPulseOutAnimator?.cancel()
+ rippleShader.shouldFadeOutRipple = false
+ visibility = VISIBLE
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ visibility = GONE
+ resetRippleAlpha()
+ }
+ })
+ start()
+ }
+ }
+ }
+
+ /**
+ * Ripple that moves animates from an outer ripple ring of
+ * startRadius => endRadius => expandedRadius
+ */
+ fun startDwellRipple(
+ startRadius: Float,
+ endRadius: Float,
+ expandedRadius: Float,
+ isDozing: Boolean
+ ) {
+ if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
+ return
+ }
+
+ // we divide by 4 because the desired startRadius and endRadius is for the ripple's outer
+ // ring see RippleShader
+ val startDwellProgress = startRadius / radius / 4f
+ val endInitialDwellProgress = endRadius / radius / 4f
+ val endExpandDwellProgress = expandedRadius / radius / 4f
+
+ val alpha = if (isDozing) aodDwellAlpha else dwellAlpha
+ val pulseOutEndAlpha = (255 * alpha).toInt()
+ val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255)
+ val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress,
+ endInitialDwellProgress).apply {
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration
addUpdateListener { animator ->
val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float
rippleShader.time = now.toFloat()
- lightReveal?.revealAmount = animator.animatedValue as Float
invalidate()
}
}
- val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
- interpolator = rippleAnimator.interpolator
- startDelay = 10
- duration = rippleAnimator.duration
- addUpdateListener { animator ->
- lightReveal?.revealAmount = animator.animatedValue as Float
- }
- }
-
- val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply {
- duration = 167
+ val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply {
+ interpolator = Interpolators.LINEAR
+ duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration
addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent(
- rippleShader.color,
- animator.animatedValue as Int
+ rippleShader.color,
+ animator.animatedValue as Int
)
invalidate()
}
}
- val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply {
- startDelay = 417
- duration = 1116
+ // slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple
+ val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress,
+ endExpandDwellProgress).apply {
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
+ addUpdateListener { animator ->
+ val now = animator.currentPlayTime
+ rippleShader.progress = animator.animatedValue as Float
+ rippleShader.time = now.toFloat()
+
+ invalidate()
+ }
+ }
+
+ val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha)
+ .apply {
+ interpolator = Interpolators.LINEAR
+ duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
+ addUpdateListener { animator ->
+ rippleShader.color = ColorUtils.setAlphaComponent(
+ rippleShader.color,
+ animator.animatedValue as Int
+ )
+ invalidate()
+ }
+ }
+
+ val initialDwellPulseOutAnimator = AnimatorSet().apply {
+ playTogether(dwellPulseOutRippleAnimator, dwellPulseOutAlphaAnimator)
+ }
+ val expandDwellAnimator = AnimatorSet().apply {
+ playTogether(expandDwellRippleAnimator, expandDwellAlphaAnimator)
+ }
+
+ dwellPulseOutAnimator = AnimatorSet().apply {
+ playSequentially(
+ initialDwellPulseOutAnimator,
+ expandDwellAnimator
+ )
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?) {
+ retractAnimator?.cancel()
+ rippleShader.shouldFadeOutRipple = false
+ visibility = VISIBLE
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ visibility = GONE
+ resetRippleAlpha()
+ }
+ })
+ start()
+ }
+ }
+
+ /**
+ * Ripple that bursts outwards from the position of the sensor to the edges of the screen
+ */
+ fun startUnlockedRipple(onAnimationEnd: Runnable?) {
+ if (unlockedRippleInProgress) {
+ return // Ignore if ripple effect is already playing
+ }
+
+ var rippleStart = 0f
+ var alphaDuration = alphaInDuration
+ if (dwellPulseOutAnimator?.isRunning == true || retractAnimator?.isRunning == true) {
+ rippleStart = rippleShader.progress
+ alphaDuration = 0
+ dwellPulseOutAnimator?.cancel()
+ retractAnimator?.cancel()
+ }
+
+ val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply {
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
+ addUpdateListener { animator ->
+ val now = animator.currentPlayTime
+ rippleShader.progress = animator.animatedValue as Float
+ rippleShader.time = now.toFloat()
+
+ invalidate()
+ }
+ }
+
+ val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply {
+ duration = alphaDuration
addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color,
@@ -119,19 +287,18 @@
val animatorSet = AnimatorSet().apply {
playTogether(
rippleAnimator,
- revealAnimator,
- alphaInAnimator,
- alphaOutAnimator
+ alphaInAnimator
)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
- rippleInProgress = true
+ unlockedRippleInProgress = true
+ rippleShader.shouldFadeOutRipple = true
visibility = VISIBLE
}
override fun onAnimationEnd(animation: Animator?) {
onAnimationEnd?.run()
- rippleInProgress = false
+ unlockedRippleInProgress = false
visibility = GONE
}
})
@@ -139,8 +306,16 @@
animatorSet.start()
}
+ fun resetRippleAlpha() {
+ rippleShader.color = ColorUtils.setAlphaComponent(
+ rippleShader.color,
+ 255
+ )
+ }
+
fun setColor(color: Int) {
rippleShader.color = color
+ resetRippleAlpha()
}
override fun onDraw(canvas: Canvas?) {
@@ -148,7 +323,7 @@
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 1.5f
+ (1 - rippleShader.progress)) * radius * 2f
canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index e612fb4..420ae53 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -39,11 +39,9 @@
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.media.AudioAttributes;
import android.os.Handler;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -64,7 +62,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -72,12 +69,16 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import com.android.systemui.util.time.SystemClock;
+import java.util.HashSet;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -91,7 +92,7 @@
* controls/manages all UDFPS sensors. In other words, a single controller is registered with
* {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such
* as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or
- * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have
+ * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have
* {@code sensorId} parameters.
*/
@SuppressWarnings("deprecation")
@@ -115,9 +116,7 @@
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final DumpManager mDumpManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@Nullable private final Vibrator mVibrator;
- @NonNull private final Handler mMainHandler;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -125,6 +124,9 @@
@Nullable private final UdfpsHbmProvider mHbmProvider;
@NonNull private final KeyguardBypassController mKeyguardBypassController;
@NonNull private final ConfigurationController mConfigurationController;
+ @NonNull private final SystemClock mSystemClock;
+ @NonNull private final UnlockedScreenOffAnimationController
+ mUnlockedScreenOffAnimationController;
@VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -157,12 +159,14 @@
private Runnable mAodInterruptRunnable;
private boolean mOnFingerDown;
private boolean mAttemptedToDismissKeyguard;
+ private Set<Callback> mCallbacks = new HashSet<>();
@VisibleForTesting
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 =
@@ -238,7 +242,7 @@
final UdfpsEnrollHelper enrollHelper;
if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
|| reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
- enrollHelper = new UdfpsEnrollHelper(mContext, reason);
+ enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason);
} else {
enrollHelper = null;
}
@@ -449,19 +453,19 @@
final String touchInfo = String.format(
"minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
minor, major, v, exceedsVelocityThreshold);
- final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime;
+ final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
if (!isIlluminationRequested && !mGoodCaptureReceived &&
!exceedsVelocityThreshold) {
onFingerDown((int) event.getRawX(), (int) event.getRawY(), minor,
major);
Log.v(TAG, "onTouch | finger down: " + touchInfo);
- mTouchLogTime = SystemClock.elapsedRealtime();
- mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ mTouchLogTime = mSystemClock.elapsedRealtime();
+ mPowerManager.userActivity(mSystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
handled = true;
} else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
Log.v(TAG, "onTouch | finger move: " + touchInfo);
- mTouchLogTime = SystemClock.elapsedRealtime();
+ mTouchLogTime = mSystemClock.elapsedRealtime();
}
} else {
Log.v(TAG, "onTouch | finger outside");
@@ -512,7 +516,6 @@
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -525,11 +528,12 @@
@NonNull KeyguardBypassController keyguardBypassController,
@NonNull DisplayManager displayManager,
@Main Handler mainHandler,
- @NonNull ConfigurationController configurationController) {
+ @NonNull ConfigurationController configurationController,
+ @NonNull SystemClock systemClock,
+ @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mContext = context;
mExecution = execution;
// TODO (b/185124905): inject main handler and vibrator once done prototyping
- mMainHandler = new Handler(Looper.getMainLooper());
mVibrator = vibrator;
mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -543,7 +547,6 @@
mKeyguardViewManager = statusBarKeyguardViewManager;
mDumpManager = dumpManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardViewMediator = keyguardViewMediator;
mFalsingManager = falsingManager;
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
@@ -561,6 +564,8 @@
mainHandler);
mKeyguardBypassController = keyguardBypassController;
mConfigurationController = configurationController;
+ mSystemClock = systemClock;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -650,6 +655,20 @@
}
}
+ private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) {
+ if (!(animation instanceof UdfpsKeyguardViewController)) {
+ // always rotate view if we're not on the keyguard
+ return true;
+ }
+
+ // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded
+ if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) {
+ return false;
+ }
+
+ return true;
+ }
+
private WindowManager.LayoutParams computeLayoutParams(
@Nullable UdfpsAnimationViewController animation) {
final int paddingX = animation != null ? animation.getPaddingX() : 0;
@@ -673,9 +692,11 @@
// Transform dimensions if the device is in landscape mode
switch (mContext.getDisplay().getRotation()) {
case Surface.ROTATION_90:
- if (animation instanceof UdfpsKeyguardViewController
- && mKeyguardUpdateMonitor.isGoingToSleep()) {
+ if (!shouldRotate(animation)) {
+ Log.v(TAG, "skip rotating udfps location ROTATION_90");
break;
+ } else {
+ Log.v(TAG, "rotate udfps location ROTATION_90");
}
mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius
- paddingX;
@@ -684,9 +705,11 @@
break;
case Surface.ROTATION_270:
- if (animation instanceof UdfpsKeyguardViewController
- && mKeyguardUpdateMonitor.isGoingToSleep()) {
+ if (!shouldRotate(animation)) {
+ Log.v(TAG, "skip rotating udfps location ROTATION_270");
break;
+ } else {
+ Log.v(TAG, "rotate udfps location ROTATION_270");
}
mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius
- paddingX;
@@ -703,15 +726,21 @@
return mCoreLayoutParams;
}
+
private void onOrientationChanged() {
// When the configuration changes it's almost always necessary to destroy and re-create
// the overlay's window to pass it the new LayoutParams.
// Hiding the overlay will destroy its window. It's safe to hide the overlay regardless
// of whether it is already hidden.
+ final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
hideUdfpsOverlay();
+
// If the overlay needs to be shown, this will re-create and show the overlay with the
// updated LayoutParams. Otherwise, the overlay will remain hidden.
updateOverlay();
+ if (wasShowingAltAuth) {
+ mKeyguardViewManager.showGenericBouncer(true);
+ }
}
private void showUdfpsOverlay(@NonNull ServerRequest request) {
@@ -759,6 +788,7 @@
UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate(
R.layout.udfps_enroll_view, null);
mView.addView(enrollView);
+ enrollView.updateSensorLocation(mSensorProps);
return new UdfpsEnrollViewController(
enrollView,
mServerRequest.mEnrollHelper,
@@ -776,11 +806,12 @@
mStatusBar,
mKeyguardViewManager,
mKeyguardUpdateMonitor,
- mFgExecutor,
mDumpManager,
- mKeyguardViewMediator,
mLockscreenShadeTransitionController,
mConfigurationController,
+ mSystemClock,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
this
);
case IUdfpsOverlayController.REASON_AUTH_BP:
@@ -816,10 +847,14 @@
Log.v(TAG, "hideUdfpsOverlay | removing window");
// Reset the controller back to its starting state.
onFingerUp();
+ boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
mWindowManager.removeView(mView);
mView.setOnTouchListener(null);
mView.setOnHoverListener(null);
mView.setAnimationViewController(null);
+ if (wasShowingAltAuth) {
+ mKeyguardViewManager.resetAlternateAuth(true);
+ }
mAccessibilityManager.removeTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
mView = null;
@@ -841,6 +876,10 @@
return;
}
+ if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ return;
+ }
+
mAodInterruptRunnable = () -> {
mIsAodInterruptActive = true;
// Since the sensor that triggers the AOD interrupt doesn't provide
@@ -860,6 +899,20 @@
}
/**
+ * Add a callback for fingerUp and fingerDown events
+ */
+ public void addCallback(Callback cb) {
+ mCallbacks.add(cb);
+ }
+
+ /**
+ * Remove callback
+ */
+ public void removeCallback(Callback cb) {
+ mCallbacks.remove(cb);
+ }
+
+ /**
* Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before
* user explicitly lifts their finger. Generally, this should be called whenever udfps fails
* or errors.
@@ -913,6 +966,10 @@
mFingerprintManager.onUiReady(mSensorProps.sensorId);
Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
});
+
+ for (Callback cb : mCallbacks) {
+ cb.onFingerDown();
+ }
}
private void onFingerUp() {
@@ -925,6 +982,9 @@
}
if (mOnFingerDown) {
mFingerprintManager.onPointerUp(mSensorProps.sensorId);
+ for (Callback cb : mCallbacks) {
+ cb.onFingerUp();
+ }
}
mOnFingerDown = false;
if (mView.isIlluminationRequested()) {
@@ -945,4 +1005,19 @@
mView.setOnTouchListener(mOnTouchListener);
}
}
+
+ /**
+ * Callback for fingerUp and fingerDown events.
+ */
+ public interface Callback {
+ /**
+ * Called onFingerUp events. Will only be called if the finger was previously down.
+ */
+ void onFingerUp();
+
+ /**
+ * Called onFingerDown events.
+ */
+ void onFingerDown();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index ea69b1d..2034ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -16,9 +16,11 @@
package com.android.systemui.biometrics;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -26,11 +28,17 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.TypedValue;
import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
/**
@@ -39,11 +47,20 @@
public class UdfpsEnrollDrawable extends UdfpsDrawable {
private static final String TAG = "UdfpsAnimationEnroll";
- private static final long ANIM_DURATION = 800;
+ private static final long HINT_COLOR_ANIM_DELAY_MS = 233L;
+ private static final long HINT_COLOR_ANIM_DURATION_MS = 517L;
+ private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L;
+ private static final long TARGET_ANIM_DURATION_LONG = 800L;
+ private static final long TARGET_ANIM_DURATION_SHORT = 600L;
// 1 + SCALE_MAX is the maximum that the moving target will animate to
private static final float SCALE_MAX = 0.25f;
- @NonNull private final UdfpsEnrollProgressBarDrawable mProgressDrawable;
+ private static final float HINT_PADDING_DP = 10f;
+ private static final float HINT_MAX_WIDTH_DP = 6f;
+ private static final float HINT_ANGLE = 40f;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
@NonNull private final Drawable mMovingTargetFpIcon;
@NonNull private final Paint mSensorOutlinePaint;
@NonNull private final Paint mBlueFill;
@@ -52,18 +69,41 @@
@Nullable private UdfpsEnrollHelper mEnrollHelper;
// Moving target animator set
- @Nullable AnimatorSet mAnimatorSet;
+ @Nullable AnimatorSet mTargetAnimatorSet;
// Moving target location
float mCurrentX;
float mCurrentY;
// Moving target size
float mCurrentScale = 1.f;
+ @ColorInt private final int mHintColorFaded;
+ @ColorInt private final int mHintColorHighlight;
+ private final float mHintMaxWidthPx;
+ private final float mHintPaddingPx;
+
+ @NonNull private final Animator.AnimatorListener mTargetAnimListener;
+
+ private boolean mShouldShowTipHint = false;
+ @NonNull private final Paint mTipHintPaint;
+ @Nullable private AnimatorSet mTipHintAnimatorSet;
+ @Nullable private ValueAnimator mTipHintColorAnimator;
+ @Nullable private ValueAnimator mTipHintWidthAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener;
+ @NonNull private final Animator.AnimatorListener mTipHintPulseListener;
+
+ private boolean mShouldShowEdgeHint = false;
+ @NonNull private final Paint mEdgeHintPaint;
+ @Nullable private AnimatorSet mEdgeHintAnimatorSet;
+ @Nullable private ValueAnimator mEdgeHintColorAnimator;
+ @Nullable private ValueAnimator mEdgeHintWidthAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener;
+ @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener;
+
UdfpsEnrollDrawable(@NonNull Context context) {
super(context);
- mProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, this);
-
mSensorOutlinePaint = new Paint(0 /* flags */);
mSensorOutlinePaint.setAntiAlias(true);
mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
@@ -80,6 +120,117 @@
mMovingTargetFpIcon.mutate();
mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
+
+ mHintColorFaded = getHintColorFaded(context);
+ mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress);
+ mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP);
+ mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP);
+
+ mTargetAnimListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateTipHintVisibility();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+
+ mTipHintPaint = new Paint(0 /* flags */);
+ mTipHintPaint.setAntiAlias(true);
+ mTipHintPaint.setColor(mHintColorFaded);
+ mTipHintPaint.setStyle(Paint.Style.STROKE);
+ mTipHintPaint.setStrokeCap(Paint.Cap.ROUND);
+ mTipHintPaint.setStrokeWidth(0f);
+ mTipHintColorUpdateListener = animation -> {
+ mTipHintPaint.setColor((int) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mTipHintWidthUpdateListener = animation -> {
+ mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mTipHintPulseListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHandler.postDelayed(() -> {
+ mTipHintColorAnimator =
+ ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded);
+ mTipHintColorAnimator.setInterpolator(new LinearInterpolator());
+ mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
+ mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
+ mTipHintColorAnimator.start();
+ }, HINT_COLOR_ANIM_DELAY_MS);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+
+ mEdgeHintPaint = new Paint(0 /* flags */);
+ mEdgeHintPaint.setAntiAlias(true);
+ mEdgeHintPaint.setColor(mHintColorFaded);
+ mEdgeHintPaint.setStyle(Paint.Style.STROKE);
+ mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND);
+ mEdgeHintPaint.setStrokeWidth(0f);
+ mEdgeHintColorUpdateListener = animation -> {
+ mEdgeHintPaint.setColor((int) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mEdgeHintWidthUpdateListener = animation -> {
+ mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mEdgeHintPulseListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHandler.postDelayed(() -> {
+ mEdgeHintColorAnimator =
+ ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded);
+ mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator());
+ mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
+ mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
+ mEdgeHintColorAnimator.start();
+ }, HINT_COLOR_ANIM_DELAY_MS);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+ }
+
+ @ColorInt
+ private static int getHintColorFaded(@NonNull Context context) {
+ final TypedValue tv = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
+ final int alpha = (int) (tv.getFloat() * 255f);
+
+ final int[] attrs = new int[] {android.R.attr.colorControlNormal};
+ final TypedArray ta = context.obtainStyledAttributes(attrs);
+ try {
+ @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled));
+ return ColorUtils.setAlphaComponent(color, alpha);
+ } finally {
+ ta.recycle();
+ }
}
void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
@@ -100,59 +251,164 @@
}
void onEnrollmentProgress(int remaining, int totalSteps) {
- mProgressDrawable.setEnrollmentProgress(remaining, totalSteps);
+ if (mEnrollHelper == null) {
+ return;
+ }
- if (mEnrollHelper.isCenterEnrollmentComplete()) {
- if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
- mAnimatorSet.end();
+ if (!mEnrollHelper.isCenterEnrollmentStage()) {
+ if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
+ mTargetAnimatorSet.end();
}
final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
+ if (mCurrentX != point.x || mCurrentY != point.y) {
+ final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
+ x.addUpdateListener(animation -> {
+ mCurrentX = (float) animation.getAnimatedValue();
+ invalidateSelf();
+ });
- final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
- x.addUpdateListener(animation -> {
- mCurrentX = (float) animation.getAnimatedValue();
- invalidateSelf();
- });
+ final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
+ y.addUpdateListener(animation -> {
+ mCurrentY = (float) animation.getAnimatedValue();
+ invalidateSelf();
+ });
- final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
- y.addUpdateListener(animation -> {
- mCurrentY = (float) animation.getAnimatedValue();
- invalidateSelf();
- });
+ final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
+ final long duration = isMovingToCenter
+ ? TARGET_ANIM_DURATION_SHORT
+ : TARGET_ANIM_DURATION_LONG;
- final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
- scale.setDuration(ANIM_DURATION);
- scale.addUpdateListener(animation -> {
- // Grow then shrink
- mCurrentScale = 1 +
- SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
- invalidateSelf();
- });
+ final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
+ scale.setDuration(duration);
+ scale.addUpdateListener(animation -> {
+ // Grow then shrink
+ mCurrentScale = 1
+ + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
+ invalidateSelf();
+ });
- mAnimatorSet = new AnimatorSet();
+ mTargetAnimatorSet = new AnimatorSet();
- mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
- mAnimatorSet.setDuration(ANIM_DURATION);
- mAnimatorSet.playTogether(x, y, scale);
- mAnimatorSet.start();
+ mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mTargetAnimatorSet.setDuration(duration);
+ mTargetAnimatorSet.addListener(mTargetAnimListener);
+ mTargetAnimatorSet.playTogether(x, y, scale);
+ mTargetAnimatorSet.start();
+ } else {
+ updateTipHintVisibility();
+ }
+ } else {
+ updateTipHintVisibility();
+ }
+
+ updateEdgeHintVisibility();
+ }
+
+ private void updateTipHintVisibility() {
+ final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
+ if (mShouldShowTipHint == shouldShow) {
+ return;
+ }
+ mShouldShowTipHint = shouldShow;
+
+ if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) {
+ mTipHintWidthAnimator.cancel();
+ }
+
+ final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
+ mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth);
+ mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener);
+
+ if (shouldShow) {
+ startTipHintPulseAnimation();
+ } else {
+ mTipHintWidthAnimator.start();
}
}
- void onLastStepAcquired() {
- mProgressDrawable.onLastStepAcquired();
+ private void updateEdgeHintVisibility() {
+ final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
+ if (mShouldShowEdgeHint == shouldShow) {
+ return;
+ }
+ mShouldShowEdgeHint = shouldShow;
+
+ if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) {
+ mEdgeHintWidthAnimator.cancel();
+ }
+
+ final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
+ mEdgeHintWidthAnimator =
+ ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth);
+ mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener);
+
+ if (shouldShow) {
+ startEdgeHintPulseAnimation();
+ } else {
+ mEdgeHintWidthAnimator.start();
+ }
+ }
+
+ private void startTipHintPulseAnimation() {
+ mHandler.removeCallbacksAndMessages(null);
+ if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) {
+ mTipHintAnimatorSet.cancel();
+ }
+ if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) {
+ mTipHintColorAnimator.cancel();
+ }
+
+ mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight);
+ mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
+ mTipHintColorAnimator.addListener(mTipHintPulseListener);
+
+ mTipHintAnimatorSet = new AnimatorSet();
+ mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator);
+ mTipHintAnimatorSet.start();
+ }
+
+ private void startEdgeHintPulseAnimation() {
+ mHandler.removeCallbacksAndMessages(null);
+ if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) {
+ mEdgeHintAnimatorSet.cancel();
+ }
+ if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) {
+ mEdgeHintColorAnimator.cancel();
+ }
+
+ mEdgeHintColorAnimator =
+ ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight);
+ mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
+ mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener);
+
+ mEdgeHintAnimatorSet = new AnimatorSet();
+ mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator);
+ mEdgeHintAnimatorSet.start();
+ }
+
+ private boolean isTipHintVisible() {
+ return mTipHintPaint.getStrokeWidth() > 0f;
+ }
+
+ private boolean isEdgeHintVisible() {
+ return mEdgeHintPaint.getStrokeWidth() > 0f;
}
@Override
public void draw(@NonNull Canvas canvas) {
- mProgressDrawable.draw(canvas);
-
if (isIlluminationShowing()) {
return;
}
// Draw moving target
- if (mEnrollHelper.isCenterEnrollmentComplete()) {
+ if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
canvas.save();
canvas.translate(mCurrentX, mCurrentY);
@@ -172,11 +428,59 @@
mFingerprintDrawable.setAlpha(mAlpha);
mSensorOutlinePaint.setAlpha(mAlpha);
}
- }
- @Override
- public void onBoundsChange(@NonNull Rect rect) {
- mProgressDrawable.setBounds(rect);
+ // Draw the finger tip or edges hint.
+ if (isTipHintVisible() || isEdgeHintVisible()) {
+ canvas.save();
+
+ // Make arcs start from the top, rather than the right.
+ canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
+
+ final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f;
+ final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f;
+ final float hintXOffset = halfSensorWidth + mHintPaddingPx;
+ final float hintYOffset = halfSensorHeight + mHintPaddingPx;
+
+ if (isTipHintVisible()) {
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mTipHintPaint);
+ }
+
+ if (isEdgeHintVisible()) {
+ // Draw right edge hint.
+ canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mEdgeHintPaint);
+
+ // Draw left edge hint.
+ canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY());
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mEdgeHintPaint);
+ }
+
+ canvas.restore();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 6a918a6..caec1d5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PointF;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Build;
import android.os.UserHandle;
@@ -44,15 +45,14 @@
private static final String NEW_COORDS_OVERRIDE =
"com.android.systemui.biometrics.UdfpsNewCoords";
- // Enroll with two center touches before going to guided enrollment
- private static final int NUM_CENTER_TOUCHES = 2;
-
interface Listener {
void onEnrollmentProgress(int remaining, int totalSteps);
+ void onEnrollmentHelp(int remaining, int totalSteps);
void onLastStepAcquired();
}
@NonNull private final Context mContext;
+ @NonNull private final FingerprintManager mFingerprintManager;
// IUdfpsOverlayController reason
private final int mEnrollReason;
private final boolean mAccessibilityEnabled;
@@ -65,10 +65,15 @@
// interface makes no promises about monotonically increasing by one each time.
private int mLocationsEnrolled = 0;
+ private int mCenterTouchCount = 0;
+
@Nullable Listener mListener;
- public UdfpsEnrollHelper(@NonNull Context context, int reason) {
+ public UdfpsEnrollHelper(@NonNull Context context,
+ @NonNull FingerprintManager fingerprintManager, int reason) {
+
mContext = context;
+ mFingerprintManager = fingerprintManager;
mEnrollReason = reason;
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
@@ -117,6 +122,14 @@
}
}
+ int getStageCount() {
+ return mFingerprintManager.getEnrollStageCount();
+ }
+
+ int getStageThresholdSteps(int totalSteps, int stageIndex) {
+ return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
+ }
+
boolean shouldShowProgressBar() {
return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
}
@@ -128,6 +141,9 @@
if (remaining != mRemainingSteps) {
mLocationsEnrolled++;
+ if (isCenterEnrollmentStage()) {
+ mCenterTouchCount++;
+ }
}
mRemainingSteps = remaining;
@@ -138,7 +154,9 @@
}
void onEnrollmentHelp() {
-
+ if (mListener != null) {
+ mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
+ }
}
void setListener(Listener listener) {
@@ -152,19 +170,41 @@
}
}
- boolean isCenterEnrollmentComplete() {
+ boolean isCenterEnrollmentStage() {
if (mTotalSteps == -1 || mRemainingSteps == -1) {
- return false;
- } else if (mAccessibilityEnabled) {
+ return true;
+ }
+ return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
+ }
+
+ boolean isGuidedEnrollmentStage() {
+ if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
return false;
}
- final int stepsEnrolled = mTotalSteps - mRemainingSteps;
- return stepsEnrolled >= NUM_CENTER_TOUCHES;
+ final int progressSteps = mTotalSteps - mRemainingSteps;
+ return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
+ && progressSteps < getStageThresholdSteps(mTotalSteps, 1);
+ }
+
+ boolean isTipEnrollmentStage() {
+ if (mTotalSteps == -1 || mRemainingSteps == -1) {
+ return false;
+ }
+ final int progressSteps = mTotalSteps - mRemainingSteps;
+ return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
+ && progressSteps < getStageThresholdSteps(mTotalSteps, 2);
+ }
+
+ boolean isEdgeEnrollmentStage() {
+ if (mTotalSteps == -1 || mRemainingSteps == -1) {
+ return false;
+ }
+ return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
}
@NonNull
PointF getNextGuidedEnrollmentPoint() {
- if (mAccessibilityEnabled) {
+ if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
return new PointF(0f, 0f);
}
@@ -174,7 +214,7 @@
SCALE_OVERRIDE, SCALE,
UserHandle.USER_CURRENT);
}
- final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES;
+ final int index = mLocationsEnrolled - mCenterTouchCount;
final PointF originalPoint = mGuidedEnrollmentPoints
.get(index % mGuidedEnrollmentPoints.size());
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 4195009..b2a5409 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -16,116 +16,129 @@
package com.android.systemui.biometrics;
-import android.animation.ValueAnimator;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
-import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.Log;
-import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.systemui.R;
+import java.util.ArrayList;
+import java.util.List;
/**
* UDFPS enrollment progress bar.
*/
public class UdfpsEnrollProgressBarDrawable extends Drawable {
+ private static final String TAG = "UdfpsProgressBar";
- private static final String TAG = "UdfpsEnrollProgressBarDrawable";
-
- private static final float PROGRESS_BAR_THICKNESS_DP = 12;
+ private static final float SEGMENT_GAP_ANGLE = 12f;
@NonNull private final Context mContext;
- @NonNull private final UdfpsEnrollDrawable mParent;
- @NonNull private final Paint mBackgroundCirclePaint;
- @NonNull private final Paint mProgressPaint;
- @Nullable private ValueAnimator mProgressAnimator;
- private float mProgress;
- private int mRotation; // After last step, rotate the progress bar once
- private boolean mLastStepAcquired;
+ @Nullable private UdfpsEnrollHelper mEnrollHelper;
+ @NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
+ private int mTotalSteps = 1;
+ private int mProgressSteps = 0;
+ private boolean mIsShowingHelp = false;
- public UdfpsEnrollProgressBarDrawable(@NonNull Context context,
- @NonNull UdfpsEnrollDrawable parent) {
+ public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
mContext = context;
- mParent = parent;
-
- mBackgroundCirclePaint = new Paint();
- mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
- mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled));
- mBackgroundCirclePaint.setAntiAlias(true);
- mBackgroundCirclePaint.setStyle(Paint.Style.STROKE);
-
- // Background circle color + alpha
- TypedArray tc = context.obtainStyledAttributes(
- new int[] {android.R.attr.colorControlNormal});
- int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor());
- mBackgroundCirclePaint.setColor(tintColor);
- tc.recycle();
- TypedValue alpha = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
- mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255));
-
- // Progress should not be color extracted
- mProgressPaint = new Paint();
- mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
- mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress));
- mProgressPaint.setAntiAlias(true);
- mProgressPaint.setStyle(Paint.Style.STROKE);
- mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
}
- void setEnrollmentProgress(int remaining, int totalSteps) {
- // Add one so that the first steps actually changes progress, but also so that the last
- // step ends at 1.0
- final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1);
- setEnrollmentProgress(progress);
+ void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) {
+ mEnrollHelper = enrollHelper;
+ if (enrollHelper != null) {
+ final int stageCount = enrollHelper.getStageCount();
+ mSegments = new ArrayList<>(stageCount);
+ float startAngle = SEGMENT_GAP_ANGLE / 2f;
+ final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE;
+ final Runnable invalidateRunnable = this::invalidateSelf;
+ for (int index = 0; index < stageCount; index++) {
+ mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle,
+ sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
+ startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
+ }
+ invalidateSelf();
+ }
}
- private void setEnrollmentProgress(float progress) {
- if (mLastStepAcquired) {
- return;
- }
+ void onEnrollmentProgress(int remaining, int totalSteps) {
+ mTotalSteps = totalSteps;
+ updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */);
+ }
- long animationDuration = 150;
-
- if (progress == 1.f) {
- animationDuration = 400;
- final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400);
- rotationAnimator.setDuration(animationDuration);
- rotationAnimator.addUpdateListener(animation -> {
- Log.d(TAG, "Rotation: " + mRotation);
- mRotation = (int) animation.getAnimatedValue();
- mParent.invalidateSelf();
- });
- rotationAnimator.start();
- }
-
- if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
- mProgressAnimator.cancel();
- }
-
- mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
- mProgressAnimator.setDuration(animationDuration);
- mProgressAnimator.addUpdateListener(animation -> {
- mProgress = (float) animation.getAnimatedValue();
- // Use the parent to invalidate, since it's the one that's attached as the view's
- // drawable and has its callback set automatically. Invalidating via
- // `this.invalidateSelf` actually does not invoke draw(), since this drawable's callback
- // is not really set.
- mParent.invalidateSelf();
- });
- mProgressAnimator.start();
+ void onEnrollmentHelp(int remaining, int totalSteps) {
+ updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */);
}
void onLastStepAcquired() {
- setEnrollmentProgress(1.f);
- mLastStepAcquired = true;
+ updateState(mTotalSteps, false /* isShowingHelp */);
+ }
+
+ private static int getProgressSteps(int remaining, int totalSteps) {
+ // Show some progress for the initial touch.
+ return Math.max(1, totalSteps - remaining);
+ }
+
+ private void updateState(int progressSteps, boolean isShowingHelp) {
+ updateProgress(progressSteps);
+ updateFillColor(isShowingHelp);
+ }
+
+ private void updateProgress(int progressSteps) {
+ if (mProgressSteps == progressSteps) {
+ return;
+ }
+ mProgressSteps = progressSteps;
+
+ if (mEnrollHelper == null) {
+ Log.e(TAG, "updateState: UDFPS enroll helper was null");
+ return;
+ }
+
+ int index = 0;
+ int prevThreshold = 0;
+ while (index < mSegments.size()) {
+ final UdfpsEnrollProgressBarSegment segment = mSegments.get(index);
+ final int thresholdSteps = mEnrollHelper.getStageThresholdSteps(mTotalSteps, index);
+ if (progressSteps >= thresholdSteps && segment.getProgress() < 1f) {
+ segment.updateProgress(1f);
+ break;
+ } else if (progressSteps >= prevThreshold && progressSteps < thresholdSteps) {
+ final int relativeSteps = progressSteps - prevThreshold;
+ final int relativeThreshold = thresholdSteps - prevThreshold;
+ final float segmentProgress = (float) relativeSteps / (float) relativeThreshold;
+ segment.updateProgress(segmentProgress);
+ break;
+ }
+
+ index++;
+ prevThreshold = thresholdSteps;
+ }
+
+ if (progressSteps >= mTotalSteps) {
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.startCompletionAnimation();
+ }
+ } else {
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.cancelCompletionAnimation();
+ }
+ }
+ }
+
+ private void updateFillColor(boolean isShowingHelp) {
+ if (mIsShowingHelp == isShowingHelp) {
+ return;
+ }
+ mIsShowingHelp = isShowingHelp;
+
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.updateFillColor(isShowingHelp);
+ }
}
@Override
@@ -133,43 +146,22 @@
canvas.save();
// Progress starts from the top, instead of the right
- canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY());
+ canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
- // Progress bar "background track"
- final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2;
- canvas.drawArc(halfPaddingPx,
- halfPaddingPx,
- getBounds().right - halfPaddingPx,
- getBounds().bottom - halfPaddingPx,
- 0,
- 360,
- false,
- mBackgroundCirclePaint
- );
-
- final float progress = 360.f * mProgress;
- // Progress
- canvas.drawArc(halfPaddingPx,
- halfPaddingPx,
- getBounds().right - halfPaddingPx,
- getBounds().bottom - halfPaddingPx,
- 0,
- progress,
- false,
- mProgressPaint
- );
+ // Draw each of the enroll segments.
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.draw(canvas);
+ }
canvas.restore();
}
@Override
public void setAlpha(int alpha) {
-
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
-
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
new file mode 100644
index 0000000..bd6ab44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
@@ -0,0 +1,280 @@
+/*
+ * 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.biometrics;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * A single segment of the UDFPS enrollment progress bar.
+ */
+public class UdfpsEnrollProgressBarSegment {
+ private static final String TAG = "UdfpsProgressBarSegment";
+
+ private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
+ private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
+ private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L;
+ private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L;
+
+ private static final float STROKE_WIDTH_DP = 12f;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @NonNull private final Rect mBounds;
+ @NonNull private final Runnable mInvalidateRunnable;
+ private final float mStartAngle;
+ private final float mSweepAngle;
+ private final float mMaxOverSweepAngle;
+ private final float mStrokeWidthPx;
+ @ColorInt private final int mProgressColor;
+ @ColorInt private final int mHelpColor;
+
+ @NonNull private final Paint mBackgroundPaint;
+ @NonNull private final Paint mProgressPaint;
+
+ private float mProgress = 0f;
+ private float mAnimatedProgress = 0f;
+ @Nullable private ValueAnimator mProgressAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
+
+ private boolean mIsShowingHelp = false;
+ @Nullable private ValueAnimator mFillColorAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
+
+ private float mOverSweepAngle = 0f;
+ @Nullable private ValueAnimator mOverSweepAnimator;
+ @Nullable private ValueAnimator mOverSweepReverseAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener;
+ @NonNull private final Runnable mOverSweepAnimationRunnable;
+
+ public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds,
+ float startAngle, float sweepAngle, float maxOverSweepAngle,
+ @NonNull Runnable invalidateRunnable) {
+
+ mBounds = bounds;
+ mInvalidateRunnable = invalidateRunnable;
+ mStartAngle = startAngle;
+ mSweepAngle = sweepAngle;
+ mMaxOverSweepAngle = maxOverSweepAngle;
+ mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
+ mProgressColor = context.getColor(R.color.udfps_enroll_progress);
+ mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+
+ mBackgroundPaint = new Paint();
+ mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
+ mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
+ mBackgroundPaint.setAntiAlias(true);
+ mBackgroundPaint.setStyle(Paint.Style.STROKE);
+ mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ // Background paint color + alpha
+ final int[] attrs = new int[] {android.R.attr.colorControlNormal};
+ final TypedArray ta = context.obtainStyledAttributes(attrs);
+ @ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor());
+ mBackgroundPaint.setColor(tintColor);
+ ta.recycle();
+ TypedValue alpha = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
+ mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
+
+ // Progress should not be color extracted
+ mProgressPaint = new Paint();
+ mProgressPaint.setStrokeWidth(mStrokeWidthPx);
+ mProgressPaint.setColor(mProgressColor);
+ mProgressPaint.setAntiAlias(true);
+ mProgressPaint.setStyle(Paint.Style.STROKE);
+ mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mProgressUpdateListener = animation -> {
+ mAnimatedProgress = (float) animation.getAnimatedValue();
+ mInvalidateRunnable.run();
+ };
+
+ mFillColorUpdateListener = animation -> {
+ mProgressPaint.setColor((int) animation.getAnimatedValue());
+ mInvalidateRunnable.run();
+ };
+
+ mOverSweepUpdateListener = animation -> {
+ mOverSweepAngle = (float) animation.getAnimatedValue();
+ mInvalidateRunnable.run();
+ };
+ mOverSweepAnimationRunnable = () -> {
+ if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
+ mOverSweepAnimator.cancel();
+ }
+ mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle);
+ mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS);
+ mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener);
+ mOverSweepAnimator.start();
+ };
+ }
+
+ /**
+ * Draws this segment to the given canvas.
+ */
+ public void draw(@NonNull Canvas canvas) {
+ final float halfPaddingPx = mStrokeWidthPx / 2f;
+
+ if (mAnimatedProgress < 1f) {
+ // Draw the unfilled background color of the segment.
+ canvas.drawArc(
+ halfPaddingPx,
+ halfPaddingPx,
+ mBounds.right - halfPaddingPx,
+ mBounds.bottom - halfPaddingPx,
+ mStartAngle,
+ mSweepAngle,
+ false /* useCenter */,
+ mBackgroundPaint);
+ }
+
+ if (mAnimatedProgress > 0f) {
+ // Draw the filled progress portion of the segment.
+ canvas.drawArc(
+ halfPaddingPx,
+ halfPaddingPx,
+ mBounds.right - halfPaddingPx,
+ mBounds.bottom - halfPaddingPx,
+ mStartAngle,
+ mSweepAngle * mAnimatedProgress + mOverSweepAngle,
+ false /* useCenter */,
+ mProgressPaint);
+ }
+ }
+
+ /**
+ * @return The fill progress of this segment, in the range [0, 1]. If fill progress is being
+ * animated, returns the value it is animating to.
+ */
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Updates the fill progress of this segment, animating if necessary.
+ *
+ * @param progress The new fill progress, in the range [0, 1].
+ */
+ public void updateProgress(float progress) {
+ updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS);
+ }
+
+ private void updateProgress(float progress, long animationDurationMs) {
+ if (mProgress == progress) {
+ return;
+ }
+ mProgress = progress;
+
+ if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
+ mProgressAnimator.cancel();
+ }
+
+ mProgressAnimator = ValueAnimator.ofFloat(mAnimatedProgress, progress);
+ mProgressAnimator.setDuration(animationDurationMs);
+ mProgressAnimator.addUpdateListener(mProgressUpdateListener);
+ mProgressAnimator.start();
+ }
+
+ /**
+ * Updates the fill color of this segment, animating if necessary.
+ *
+ * @param isShowingHelp Whether fill color should indicate that a help message is being shown.
+ */
+ public void updateFillColor(boolean isShowingHelp) {
+ if (mIsShowingHelp == isShowingHelp) {
+ return;
+ }
+ mIsShowingHelp = isShowingHelp;
+
+ if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
+ mFillColorAnimator.cancel();
+ }
+
+ @ColorInt final int targetColor = isShowingHelp ? mHelpColor : mProgressColor;
+ mFillColorAnimator = ValueAnimator.ofArgb(mProgressPaint.getColor(), targetColor);
+ mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+ mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
+ mFillColorAnimator.start();
+ }
+
+ /**
+ * Queues and runs the completion animation for this segment.
+ */
+ public void startCompletionAnimation() {
+ final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable);
+ if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) {
+ Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback
+ + ", mOverSweepAngle = " + mOverSweepAngle);
+ return;
+ }
+
+ // Reset sweep angle back to zero if the animation is being rolled back.
+ if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
+ mOverSweepReverseAnimator.cancel();
+ mOverSweepAngle = 0f;
+ }
+
+ // Clear help color and start filling the segment if it isn't already.
+ if (mAnimatedProgress < 1f) {
+ updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS);
+ updateFillColor(false /* isShowingHelp */);
+ }
+
+ // Queue the animation to run after fill completes.
+ mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS);
+ }
+
+ /**
+ * Cancels (and reverses, if necessary) a queued or running completion animation.
+ */
+ public void cancelCompletionAnimation() {
+ // Cancel the animation if it's queued or running.
+ mHandler.removeCallbacks(mOverSweepAnimationRunnable);
+ if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
+ mOverSweepAnimator.cancel();
+ }
+
+ // Roll back the animation if it has at least partially run.
+ if (mOverSweepAngle > 0f) {
+ if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
+ mOverSweepReverseAnimator.cancel();
+ }
+
+ final float completion = mOverSweepAngle / mMaxOverSweepAngle;
+ final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion);
+ mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f);
+ mOverSweepReverseAnimator.setDuration(proratedDuration);
+ mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener);
+ mOverSweepReverseAnimator.start();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 2cdf49d..64b0968 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -17,9 +17,12 @@
package com.android.systemui.biometrics;
import android.content.Context;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
@@ -32,20 +35,25 @@
*/
public class UdfpsEnrollView extends UdfpsAnimationView {
@NonNull private final UdfpsEnrollDrawable mFingerprintDrawable;
+ @NonNull private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
@NonNull private final Handler mHandler;
@NonNull private ImageView mFingerprintView;
+ @NonNull private ImageView mFingerprintProgressView;
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
+ mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context);
mHandler = new Handler(Looper.getMainLooper());
}
@Override
protected void onFinishInflate() {
mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
+ mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
mFingerprintView.setImageDrawable(mFingerprintDrawable);
+ mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
}
@Override
@@ -53,15 +61,34 @@
return mFingerprintDrawable;
}
+ void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
+ View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
+ final int sensorHeight = sensorProps.sensorRadius * 2;
+ final int sensorWidth = sensorHeight;
+ ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
+ params.width = sensorWidth;
+ params.height = sensorHeight;
+ fingerprintAccessibilityView.setLayoutParams(params);
+ fingerprintAccessibilityView.requestLayout();
+ }
+
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
+ mFingerprintProgressDrawable.setEnrollHelper(enrollHelper);
mFingerprintDrawable.setEnrollHelper(enrollHelper);
}
void onEnrollmentProgress(int remaining, int totalSteps) {
- mHandler.post(() -> mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps));
+ mHandler.post(() -> {
+ mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
+ mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
+ });
+ }
+
+ void onEnrollmentHelp(int remaining, int totalSteps) {
+ mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
}
void onLastStepAcquired() {
- mHandler.post(mFingerprintDrawable::onLastStepAcquired);
+ mHandler.post(mFingerprintProgressDrawable::onLastStepAcquired);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 3dab010..6cdd1c8b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -33,16 +33,21 @@
@NonNull private final UdfpsEnrollHelper mEnrollHelper;
@NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
new UdfpsEnrollHelper.Listener() {
- @Override
- public void onEnrollmentProgress(int remaining, int totalSteps) {
- mView.onEnrollmentProgress(remaining, totalSteps);
- }
+ @Override
+ public void onEnrollmentProgress(int remaining, int totalSteps) {
+ mView.onEnrollmentProgress(remaining, totalSteps);
+ }
- @Override
- public void onLastStepAcquired() {
- mView.onLastStepAcquired();
- }
- };
+ @Override
+ public void onEnrollmentHelp(int remaining, int totalSteps) {
+ mView.onEnrollmentHelp(remaining, totalSteps);
+ }
+
+ @Override
+ public void onLastStepAcquired() {
+ mView.onLastStepAcquired();
+ }
+ };
protected UdfpsEnrollViewController(
@NonNull UdfpsEnrollView view,
@@ -74,7 +79,7 @@
@NonNull
@Override
public PointF getTouchTranslation() {
- if (!mEnrollHelper.isCenterEnrollmentComplete()) {
+ if (!mEnrollHelper.isGuidedEnrollmentStage()) {
return new PointF(0, 0);
} else {
return mEnrollHelper.getNextGuidedEnrollmentPoint();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index d46426a..9015396 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -155,6 +155,13 @@
updateAlpha();
}
+ /**
+ * @return alpha between 0 and 255
+ */
+ int getUnpausedAlpha() {
+ return mAlpha;
+ }
+
@Override
protected int updateAlpha() {
int alpha = super.updateAlpha();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 4896305..c128e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -26,31 +26,33 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.time.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-
/**
* Class that coordinates non-HBM animations during keyguard authentication.
*/
public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final DelayableExecutor mExecutor;
- @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
@NonNull private final ConfigurationController mConfigurationController;
+ @NonNull private final SystemClock mSystemClock;
+ @NonNull private final KeyguardStateController mKeyguardStateController;
@NonNull private final UdfpsController mUdfpsController;
+ @NonNull private final UnlockedScreenOffAnimationController
+ mUnlockedScreenOffAnimationController;
private boolean mShowingUdfpsBouncer;
private boolean mUdfpsRequested;
@@ -59,6 +61,9 @@
private int mStatusBarState;
private float mTransitionToFullShadeProgress;
private float mLastDozeAmount;
+ private long mLastUdfpsBouncerShowTime = -1;
+ private float mStatusBarExpansion;
+ private boolean mLaunchTransitionFadingAway;
/**
* hidden amount of pin/pattern/password bouncer
@@ -74,20 +79,22 @@
@NonNull StatusBar statusBar,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull DelayableExecutor mainDelayableExecutor,
@NonNull DumpManager dumpManager,
- @NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull LockscreenShadeTransitionController transitionController,
@NonNull ConfigurationController configurationController,
+ @NonNull SystemClock systemClock,
+ @NonNull KeyguardStateController keyguardStateController,
+ @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@NonNull UdfpsController udfpsController) {
super(view, statusBarStateController, statusBar, dumpManager);
mKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mExecutor = mainDelayableExecutor;
- mKeyguardViewMediator = keyguardViewMediator;
mLockScreenShadeTransitionController = transitionController;
mConfigurationController = configurationController;
+ mSystemClock = systemClock;
+ mKeyguardStateController = keyguardStateController;
mUdfpsController = udfpsController;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
}
@Override
@@ -96,6 +103,12 @@
}
@Override
+ public void onInit() {
+ super.onInit();
+ mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+ }
+
+ @Override
protected void onViewAttached() {
super.onViewAttached();
final float dozeAmount = mStatusBarStateController.getDozeAmount();
@@ -105,16 +118,20 @@
mUdfpsRequested = false;
+ mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
mStatusBarState = mStatusBarStateController.getState();
mQsExpanded = mKeyguardViewManager.isQsExpanded();
mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN;
mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing();
mConfigurationController.addCallback(mConfigurationListener);
+ mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener);
updateAlpha();
updatePauseAuth();
mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
+ mUnlockedScreenOffAnimationController.addCallback(mUnlockedScreenOffCallback);
}
@Override
@@ -122,13 +139,16 @@
super.onViewDetached();
mFaceDetectRunning = false;
+ mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
mStatusBarStateController.removeCallback(mStateListener);
mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
mConfigurationController.removeCallback(mConfigurationListener);
+ mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener);
if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
}
+ mUnlockedScreenOffAnimationController.removeCallback(mUnlockedScreenOffCallback);
}
@Override
@@ -140,9 +160,11 @@
pw.println("mQsExpanded=" + mQsExpanded);
pw.println("mIsBouncerVisible=" + mIsBouncerVisible);
pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
- pw.println("mAlpha=" + mView.getAlpha());
+ pw.println("mStatusBarExpansion=" + mStatusBarExpansion);
+ pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
pw.println("mUdfpsRequested=" + mUdfpsRequested);
pw.println("mView.mUdfpsRequested=" + mView.mUdfpsRequested);
+ pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
}
/**
@@ -154,10 +176,13 @@
return false;
}
+ boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
mShowingUdfpsBouncer = show;
- updatePauseAuth();
if (mShowingUdfpsBouncer) {
- if (mStatusBarState == StatusBarState.SHADE_LOCKED) {
+ mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
+ }
+ if (mShowingUdfpsBouncer) {
+ if (udfpsAffordanceWasNotShowing) {
mView.animateInUdfpsBouncer(null);
}
@@ -170,6 +195,8 @@
} else {
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
}
+ updateAlpha();
+ updatePauseAuth();
return true;
}
@@ -189,6 +216,10 @@
return false;
}
+ if (mLaunchTransitionFadingAway) {
+ return true;
+ }
+
if (mStatusBarState != KEYGUARD) {
return true;
}
@@ -218,16 +249,25 @@
* If we were previously showing the udfps bouncer, hide it and instead show the regular
* (pin/pattern/password) bouncer.
*
- * Does nothing if we weren't previously showing the udfps bouncer.
+ * Does nothing if we weren't previously showing the UDFPS bouncer.
*/
private void maybeShowInputBouncer() {
- if (mShowingUdfpsBouncer) {
+ if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
mKeyguardViewManager.showBouncer(true);
mKeyguardViewManager.resetAlternateAuth(false);
}
}
/**
+ * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
+ * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
+ * bouncer.
+ */
+ private boolean hasUdfpsBouncerShownWithMinTime() {
+ return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
+ }
+
+ /**
* Set the progress we're currently transitioning to the full shade. 0.0f means we're not
* transitioning yet, while 1.0f means we've fully dragged down.
*/
@@ -237,12 +277,17 @@
}
private void updateAlpha() {
- // fade icon on transition to showing bouncer
+ // fade icon on transitions to showing the status bar, but if mUdfpsRequested, then
+ // the keyguard is occluded by some application - so instead use the input bouncer
+ // hidden amount to determine the fade
+ float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mStatusBarExpansion;
int alpha = mShowingUdfpsBouncer ? 255
: (int) MathUtils.constrain(
- MathUtils.map(.5f, .9f, 0f, 255f, mInputBouncerHiddenAmount),
+ MathUtils.map(.5f, .9f, 0f, 255f, expansion),
0f, 255f);
- alpha *= (1.0f - mTransitionToFullShadeProgress);
+ if (!mShowingUdfpsBouncer) {
+ alpha *= (1.0f - mTransitionToFullShadeProgress);
+ }
mView.setUnpausedAlpha(alpha);
}
@@ -287,6 +332,7 @@
public void requestUdfps(boolean request, int color) {
mUdfpsRequested = request;
mView.requestUdfps(request, color);
+ updateAlpha();
updatePauseAuth();
}
@@ -356,4 +402,26 @@
mView.updateColor();
}
};
+
+ private final StatusBar.ExpansionChangedListener mStatusBarExpansionChangedListener =
+ new StatusBar.ExpansionChangedListener() {
+ @Override
+ public void onExpansionChanged(float expansion, boolean expanded) {
+ mStatusBarExpansion = expansion;
+ updateAlpha();
+ }
+ };
+
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onLaunchTransitionFadingAwayChanged() {
+ mLaunchTransitionFadingAway =
+ mKeyguardStateController.isLaunchTransitionFadingAway();
+ updatePauseAuth();
+ }
+ };
+
+ private final UnlockedScreenOffAnimationController.Callback mUnlockedScreenOffCallback =
+ (linear, eased) -> mStateListener.onDozeAmountChanged(linear, eased);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index e1349f2..40c28fa 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.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 @@
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/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 4104e31..46a03e8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -16,6 +16,10 @@
package com.android.systemui.controls.ui
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
@@ -23,18 +27,25 @@
import android.view.WindowInsets.Type
import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
/**
- * Displays Device Controls inside an activity
+ * Displays Device Controls inside an activity. This activity is available to be displayed over the
+ * lockscreen if the user has allowed it via
+ * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be
+ * destroyed on SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as
+ * user expectations for the activity to not continue running.
*/
class ControlsActivity @Inject constructor(
- private val uiController: ControlsUiController
+ private val uiController: ControlsUiController,
+ private val broadcastDispatcher: BroadcastDispatcher
) : LifecycleActivity() {
private lateinit var parent: ViewGroup
+ private lateinit var broadcastReceiver: BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -62,6 +73,8 @@
WindowInsets.CONSUMED
}
}
+
+ initBroadcastReceiver()
}
override fun onResume() {
@@ -83,4 +96,25 @@
uiController.hide()
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+ }
+
+ private fun initBroadcastReceiver() {
+ broadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.getAction()
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ finish()
+ }
+ }
+ }
+
+ val filter = IntentFilter()
+ filter.addAction(Intent.ACTION_SCREEN_OFF)
+ broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 557bca8..5a52fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -256,13 +256,13 @@
// Force animations when transitioning from a dialog to an activity
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
- if (keyguardStateController.isUnlocked()) {
+ if (keyguardStateController.isShowing()) {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
+ } else {
activityContext.startActivity(
intent,
ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle()
)
- } else {
- activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index c97a30e..79ee6a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -198,9 +198,11 @@
@SysUISingleton
@Provides
static ThemeOverlayApplier provideThemeOverlayManager(Context context,
- @Background Executor bgExecutor, OverlayManager overlayManager,
+ @Background Executor bgExecutor,
+ @Main Executor mainExecutor,
+ OverlayManager overlayManager,
DumpManager dumpManager) {
- return new ThemeOverlayApplier(overlayManager, bgExecutor,
+ return new ThemeOverlayApplier(overlayManager, bgExecutor, mainExecutor,
context.getString(R.string.launcher_overlayable_package),
context.getString(R.string.themepicker_overlayable_package), dumpManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 4196465..845d7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -214,6 +214,14 @@
}
/**
+ * Appends display state delayed by UDFPS event to the logs
+ * @param delayedDisplayState the display screen state that was delayed
+ */
+ public void traceDisplayStateDelayedByUdfps(int delayedDisplayState) {
+ mLogger.logDisplayStateDelayedByUdfps(delayedDisplayState);
+ }
+
+ /**
* Appends display state changed event to the logs
* @param displayState new DozeMachine state
*/
@@ -267,6 +275,13 @@
}
/**
+ * Appends sensor event dropped event to logs
+ */
+ public void traceSensorEventDropped(int sensorEvent, String reason) {
+ mLogger.logSensorEventDropped(sensorEvent, reason);
+ }
+
+ /**
* Appends pulse dropped event to logs
* @param reason why the pulse was dropped
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 9bc74be..dc18618 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -160,6 +160,14 @@
})
}
+ fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) {
+ buffer.log(TAG, INFO, {
+ str1 = Display.stateToString(delayedDisplayState)
+ }, {
+ "Delaying display state change to: $str1 due to UDFPS activity"
+ })
+ }
+
fun logDisplayStateChanged(displayState: Int) {
buffer.log(TAG, INFO, {
str1 = Display.stateToString(displayState)
@@ -197,6 +205,15 @@
})
}
+ fun logSensorEventDropped(sensorEvent: Int, reason: String) {
+ buffer.log(TAG, INFO, {
+ int1 = sensorEvent
+ str1 = reason
+ }, {
+ "SensorEvent [$int1] dropped, reason=$str1"
+ })
+ }
+
fun logPulseDropped(reason: String) {
buffer.log(TAG, INFO, {
str1 = reason
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 470d2f3..058f37a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -29,13 +29,14 @@
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
-import android.view.Display;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.dagger.BrightnessSensor;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import java.io.PrintWriter;
@@ -55,6 +56,16 @@
"com.android.systemui.doze.AOD_BRIGHTNESS";
protected static final String BRIGHTNESS_BUCKET = "brightness_bucket";
+ /**
+ * Just before the screen times out from user inactivity, DisplayPowerController dims the screen
+ * brightness to the lower of {@link #mScreenBrightnessDim}, or the current brightness minus
+ * DisplayPowerController#SCREEN_DIM_MINIMUM_REDUCTION_FLOAT.
+ *
+ * This value is 0.04f * 255, which converts SCREEN_DIM_MINIMUM_REDUCTION_FLOAT to the integer
+ * brightness values used by doze.
+ */
+ private static final int SCREEN_DIM_MINIMUM_REDUCTION_INT = 10;
+
private final Context mContext;
private final DozeMachine.Service mDozeService;
private final DozeHost mDozeHost;
@@ -63,6 +74,7 @@
private final Optional<Sensor> mLightSensorOptional;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final DozeParameters mDozeParameters;
+ private final DockManager mDockManager;
private final int[] mSensorToBrightness;
private final int[] mSensorToScrimOpacity;
private final int mScreenBrightnessDim;
@@ -81,13 +93,17 @@
*/
private int mDebugBrightnessBucket = -1;
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+
@Inject
public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
AsyncSensorManager sensorManager,
@BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler,
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
WakefulnessLifecycle wakefulnessLifecycle,
- DozeParameters dozeParameters) {
+ DozeParameters dozeParameters,
+ DockManager dockManager,
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mContext = context;
mDozeService = service;
mSensorManager = sensorManager;
@@ -96,6 +112,8 @@
mDozeParameters = dozeParameters;
mDozeHost = host;
mHandler = handler;
+ mDockManager = dockManager;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness;
@@ -107,7 +125,15 @@
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
case INITIALIZED:
+ resetBrightnessToDefault();
+ break;
+ case DOZE_AOD:
+ case DOZE_REQUEST_PULSE:
+ case DOZE_AOD_DOCKED:
+ setLightSensorEnabled(true);
+ break;
case DOZE:
+ setLightSensorEnabled(false);
resetBrightnessToDefault();
break;
case FINISH:
@@ -120,15 +146,6 @@
}
}
- @Override
- public void onScreenState(int state) {
- if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
- setLightSensorEnabled(true);
- } else {
- setLightSensorEnabled(false);
- }
- }
-
private void onDestroy() {
setLightSensorEnabled(false);
}
@@ -146,14 +163,15 @@
}
}
- private void updateBrightnessAndReady(boolean force) {
+ public void updateBrightnessAndReady(boolean force) {
if (force || mRegistered || mDebugBrightnessBucket != -1) {
int sensorValue = mDebugBrightnessBucket == -1
? mLastSensorValue : mDebugBrightnessBucket;
int brightness = computeBrightness(sensorValue);
boolean brightnessReady = brightness > 0;
if (brightnessReady) {
- mDozeService.setDozeScreenBrightness(clampToUserSetting(brightness));
+ mDozeService.setDozeScreenBrightness(
+ clampToDimBrightnessForScreenOff(clampToUserSetting(brightness)));
}
int scrimOpacity = -1;
@@ -205,13 +223,21 @@
/**
* Clamp the brightness to the dim brightness value used by PowerManagerService just before the
* device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we
- * don't raise the brightness back to the user setting before playing the screen off animation.
+ * don't raise the brightness back to the user setting before or during the screen off
+ * animation.
*/
private int clampToDimBrightnessForScreenOff(int brightness) {
- if (mDozeParameters.shouldControlUnlockedScreenOff()
+ if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
&& mWakefulnessLifecycle.getLastSleepReason()
== PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) {
- return Math.min(mScreenBrightnessDim, brightness);
+ return Math.max(
+ PowerManager.BRIGHTNESS_OFF,
+ // Use the lower of either the dim brightness, or the current brightness reduced
+ // by the minimum dim amount. This is the same logic used in
+ // DisplayPowerController#updatePowerState to apply a minimum dim amount.
+ Math.min(
+ brightness - SCREEN_DIM_MINIMUM_REDUCTION_INT,
+ mScreenBrightnessDim));
} else {
return brightness;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 8c50a16..038be48 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -26,6 +26,11 @@
import android.util.Log;
import android.view.Display;
+import androidx.annotation.Nullable;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
@@ -34,6 +39,7 @@
import com.android.systemui.util.wakelock.WakeLock;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Controls the screen when dozing.
@@ -56,23 +62,64 @@
*/
public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2500;
+ /**
+ * Add an extra delay to the transition to DOZE when udfps is current activated before
+ * the display state transitions from ON => DOZE.
+ */
+ public static final int UDFPS_DISPLAY_STATE_DELAY = 1200;
+
private final DozeMachine.Service mDozeService;
private final Handler mHandler;
private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
private final DozeParameters mParameters;
private final DozeHost mDozeHost;
+ private final AuthController mAuthController;
+ private final Provider<UdfpsController> mUdfpsControllerProvider;
+ @Nullable private UdfpsController mUdfpsController;
+ private final DozeLog mDozeLog;
+ private final DozeScreenBrightness mDozeScreenBrightness;
private int mPendingScreenState = Display.STATE_UNKNOWN;
private SettableWakeLock mWakeLock;
@Inject
- public DozeScreenState(@WrappedService DozeMachine.Service service, @Main Handler handler,
- DozeHost host, DozeParameters parameters, WakeLock wakeLock) {
+ public DozeScreenState(
+ @WrappedService DozeMachine.Service service,
+ @Main Handler handler,
+ DozeHost host,
+ DozeParameters parameters,
+ WakeLock wakeLock,
+ AuthController authController,
+ Provider<UdfpsController> udfpsControllerProvider,
+ DozeLog dozeLog,
+ DozeScreenBrightness dozeScreenBrightness) {
mDozeService = service;
mHandler = handler;
mParameters = parameters;
mDozeHost = host;
mWakeLock = new SettableWakeLock(wakeLock, TAG);
+ mAuthController = authController;
+ mUdfpsControllerProvider = udfpsControllerProvider;
+ mDozeLog = dozeLog;
+ mDozeScreenBrightness = dozeScreenBrightness;
+
+ updateUdfpsController();
+ if (mUdfpsController == null) {
+ mAuthController.addCallback(new AuthController.Callback() {
+ @Override
+ public void onAllAuthenticatorsRegistered() {
+ updateUdfpsController();
+ }
+ });
+ }
+ }
+
+ private void updateUdfpsController() {
+ if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+ mUdfpsController = mUdfpsControllerProvider.get();
+ } else {
+ mUdfpsController = null;
+ }
}
@Override
@@ -110,21 +157,28 @@
mPendingScreenState = screenState;
// Delay screen state transitions even longer while animations are running.
- boolean shouldDelayTransition = newState == DOZE_AOD
+ boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD
&& mParameters.shouldControlScreenOff() && !turningOn;
- if (shouldDelayTransition) {
+ // Delay screen state transition longer if UDFPS is actively authenticating a fp
+ boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD
+ && mUdfpsController != null && mUdfpsController.isFingerDown();
+
+ if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) {
mWakeLock.setAcquired(true);
}
if (!messagePending) {
if (DEBUG) {
Log.d(TAG, "Display state changed to " + screenState + " delayed by "
- + (shouldDelayTransition ? ENTER_DOZE_DELAY : 1));
+ + (shouldDelayTransitionEnteringDoze ? ENTER_DOZE_DELAY : 1));
}
- if (shouldDelayTransition) {
+ if (shouldDelayTransitionEnteringDoze) {
mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY);
+ } else if (shouldDelayTransitionForUDFPS) {
+ mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
+ mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
} else {
mHandler.post(mApplyPendingScreenState);
}
@@ -139,6 +193,12 @@
}
private void applyPendingScreenState() {
+ if (mUdfpsController != null && mUdfpsController.isFingerDown()) {
+ mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
+ mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
+ return;
+ }
+
applyScreenState(mPendingScreenState);
mPendingScreenState = Display.STATE_UNKNOWN;
}
@@ -147,6 +207,12 @@
if (screenState != Display.STATE_UNKNOWN) {
if (DEBUG) Log.d(TAG, "setDozeScreenState(" + screenState + ")");
mDozeService.setDozeScreenState(screenState);
+ if (screenState == Display.STATE_DOZE) {
+ // If we're entering doze, update the doze screen brightness. We might have been
+ // clamping it to the dim brightness during the screen off animation, and we should
+ // now change it to the brightness we actually want according to the sensor.
+ mDozeScreenBrightness.updateBrightnessAndReady(false /* force */);
+ }
mPendingScreenState = Display.STATE_UNKNOWN;
mWakeLock.setAcquired(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index b2db86f..55e6154 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -456,13 +456,24 @@
public void updateListening() {
if (!mConfigured || mSensor == null) return;
- if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
- && !mRegistered) {
- mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
- if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
+ if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
+ if (!mRegistered) {
+ mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
+ if (DEBUG) {
+ Log.d(TAG, "requestTriggerSensor[" + mSensor
+ + "] " + mRegistered);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "requestTriggerSensor[" + mSensor
+ + "] already registered");
+ }
+ }
} else if (mRegistered) {
final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
- if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
+ if (DEBUG) {
+ Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt);
+ }
mRegistered = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 455f3c0..756adca 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -41,6 +41,7 @@
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -92,6 +93,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor;
+ private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
private long mNotificationPulseTime;
@@ -179,7 +181,8 @@
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings, AuthController authController,
@Main DelayableExecutor mainExecutor,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ KeyguardStateController keyguardStateController) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -198,6 +201,7 @@
mAuthController = authController;
mMainExecutor = mainExecutor;
mUiEventLogger = uiEventLogger;
+ mKeyguardStateController = keyguardStateController;
}
@Override
@@ -294,6 +298,7 @@
proximityCheckThenCall((result) -> {
if (result != null && result) {
// In pocket, drop event.
+ mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near");
return;
}
if (isDoubleTap || isTap) {
@@ -302,6 +307,10 @@
}
gentleWakeUp(pulseReason);
} else if (isPickup) {
+ if (shouldDropPickupEvent()) {
+ mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded");
+ return;
+ }
gentleWakeUp(pulseReason);
} else if (isUdfpsLongPress) {
final State state = mMachine.getState();
@@ -320,7 +329,7 @@
}, true /* alreadyPerformedProxCheck */, pulseReason);
}
- if (isPickup) {
+ if (isPickup && !shouldDropPickupEvent()) {
final long timeSinceNotification =
SystemClock.elapsedRealtime() - mNotificationPulseTime;
final boolean withinVibrationThreshold =
@@ -329,6 +338,10 @@
}
}
+ private boolean shouldDropPickupEvent() {
+ return mKeyguardStateController.isOccluded();
+ }
+
private void gentleWakeUp(int reason) {
// Log screen wake up reason (lift/pickup, tap, double-tap)
Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index bfa4780..5b327bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -34,7 +34,7 @@
* See [DumpHandler] for more information on how and when this information is dumped.
*/
@SysUISingleton
-class DumpManager @Inject constructor() {
+open class DumpManager @Inject constructor() {
private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 28f63b0..6561bd5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -25,8 +25,12 @@
* Proxy to make {@link SystemProperties} easily testable.
*/
@SysUISingleton
-class SystemPropertiesHelper @Inject constructor() {
+open class SystemPropertiesHelper @Inject constructor() {
fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
+
+ fun set(name: String, value: Int) {
+ SystemProperties.set(name, value.toString())
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index bc4ced4..1b4a47e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -62,6 +62,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.view.RotationPolicy;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -172,7 +173,8 @@
SysUiState sysUiState,
@Main Handler handler,
PackageManager packageManager,
- StatusBar statusBar) {
+ StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
super(context,
windowManagerFuncs,
@@ -204,7 +206,8 @@
sysUiState,
handler,
packageManager,
- statusBar);
+ statusBar,
+ keyguardUpdateMonitor);
mLockPatternUtils = lockPatternUtils;
mKeyguardStateController = keyguardStateController;
@@ -266,7 +269,7 @@
this::getWalletViewController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(),
- getStatusBar());
+ getStatusBar(), getKeyguardUpdateMonitor(), mLockPatternUtils);
if (shouldShowLockMessage(dialog)) {
dialog.showLockMessage();
@@ -334,12 +337,13 @@
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- StatusBar statusBar) {
+ StatusBar statusBar, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockPatternUtils lockPatternUtils) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
adapter, overflowAdapter, sysuiColorExtractor, statusBarService,
notificationShadeWindowController, sysuiState, onRotateCallback,
keyguardShowing, powerAdapter, uiEventLogger, null,
- statusBar);
+ statusBar, keyguardUpdateMonitor, lockPatternUtils);
mWalletFactory = walletFactory;
// Update window attributes
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 06e7482..9ada54b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -84,6 +84,7 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
@@ -108,6 +109,7 @@
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.animation.Interpolators;
@@ -170,6 +172,11 @@
static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
+ // See NotificationManagerService#scheduleDurationReachedLocked
+ private static final long TOAST_FADE_TIME = 333;
+ // See NotificationManagerService.LONG_DELAY
+ private static final int TOAST_VISIBLE_TIME = 3500;
+
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
private final AudioManager mAudioManager;
@@ -231,6 +238,7 @@
protected Handler mMainHandler;
private int mSmallestScreenWidthDp;
private final StatusBar mStatusBar;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -338,7 +346,8 @@
SysUiState sysUiState,
@Main Handler handler,
PackageManager packageManager,
- StatusBar statusBar) {
+ StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -369,6 +378,7 @@
mMainHandler = handler;
mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
mStatusBar = statusBar;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -422,6 +432,10 @@
return mStatusBar;
}
+ protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
+ return mKeyguardUpdateMonitor;
+ }
+
/**
* Show the global actions dialog (creating if necessary)
*
@@ -653,7 +667,7 @@
mAdapter, mOverflowAdapter, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
- mInfoProvider, mStatusBar);
+ mInfoProvider, mStatusBar, mKeyguardUpdateMonitor, mLockPatternUtils);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
@@ -2122,6 +2136,8 @@
private GlobalActionsInfoProvider mInfoProvider;
private GestureDetector mGestureDetector;
private StatusBar mStatusBar;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private LockPatternUtils mLockPatternUtils;
protected ViewGroup mContainer;
@@ -2173,7 +2189,8 @@
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) {
+ @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
super(context, themeRes);
mContext = context;
mAdapter = adapter;
@@ -2188,6 +2205,8 @@
mUiEventLogger = uiEventLogger;
mInfoProvider = infoProvider;
mStatusBar = statusBar;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
mGestureDetector = new GestureDetector(mContext, mGestureListener);
@@ -2308,6 +2327,14 @@
if (mInfoProvider != null && mInfoProvider.shouldShowMessage()) {
mInfoProvider.addPanel(mContext, mContainer, mAdapter.getCount(), () -> dismiss());
}
+
+ // If user entered from the lock screen and smart lock was enabled, disable it
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
+ if (mKeyguardShowing && userHasTrust) {
+ mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+ showSmartLockDisabledMessage();
+ }
}
protected void fixNavBarClipping() {
@@ -2319,6 +2346,37 @@
contentParent.setClipToPadding(false);
}
+ private void showSmartLockDisabledMessage() {
+ // Since power menu is the top window, make a Toast-like view that will show up
+ View message = LayoutInflater.from(mContext)
+ .inflate(com.android.systemui.R.layout.global_actions_toast, mContainer, false);
+
+ // Set up animation
+ AccessibilityManager mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final int visibleTime = mAccessibilityManager.getRecommendedTimeoutMillis(
+ TOAST_VISIBLE_TIME, AccessibilityManager.FLAG_CONTENT_TEXT);
+ message.setVisibility(View.VISIBLE);
+ message.setAlpha(0f);
+ mContainer.addView(message);
+
+ // Fade in
+ message.animate()
+ .alpha(1f)
+ .setDuration(TOAST_FADE_TIME)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Then fade out
+ message.animate()
+ .alpha(0f)
+ .setDuration(TOAST_FADE_TIME)
+ .setStartDelay(visibleTime);
+ }
+ });
+ }
+
@Override
protected void onStart() {
super.onStart();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index a641ad4..c4508e0 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -97,7 +97,7 @@
int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
background.setAlpha(backgroundAlpha);
mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
- mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
+ (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
} else {
float backgroundAlpha = mContext.getResources().getFloat(
com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index d30783c..a51ec54 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -102,7 +102,6 @@
@Override
public Size reportSurfaceSize() {
- mTexture.use(null /* consumer */);
mSurfaceSize.set(mTexture.getTextureDimensions());
return new Size(mSurfaceSize.width(), mSurfaceSize.height());
}
@@ -124,6 +123,7 @@
private final WallpaperManager mWallpaperManager;
private Bitmap mBitmap;
private boolean mWcgContent;
+ private boolean mTextureUsed;
private WallpaperTexture(WallpaperManager wallpaperManager) {
mWallpaperManager = wallpaperManager;
@@ -141,6 +141,7 @@
mWallpaperManager.forgetLoadedWallpaper();
if (mBitmap != null) {
mDimensions.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ mTextureUsed = true;
} else {
Log.w(TAG, "Can't get bitmap");
}
@@ -171,6 +172,9 @@
}
private Rect getTextureDimensions() {
+ if (!mTextureUsed) {
+ mDimensions.set(mWallpaperManager.peekBitmapDimensions());
+ }
return mDimensions;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 2d215e0..a424674 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -100,8 +100,7 @@
*/
public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
boolean updateImmediately) {
- if (type == INDICATION_TYPE_NOW_PLAYING
- || type == INDICATION_TYPE_REVERSE_CHARGING) {
+ if (type == INDICATION_TYPE_REVERSE_CHARGING) {
// temporarily don't show here, instead use AmbientContainer b/181049781
return;
}
@@ -303,7 +302,6 @@
public static final int INDICATION_TYPE_TRUST = 6;
public static final int INDICATION_TYPE_RESTING = 7;
public static final int INDICATION_TYPE_USER_LOCKED = 8;
- public static final int INDICATION_TYPE_NOW_PLAYING = 9;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
@IntDef({
@@ -317,7 +315,6 @@
INDICATION_TYPE_TRUST,
INDICATION_TYPE_RESTING,
INDICATION_TYPE_USER_LOCKED,
- INDICATION_TYPE_NOW_PLAYING,
INDICATION_TYPE_REVERSE_CHARGING,
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ee3d40e..1a8af3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -818,6 +818,7 @@
private final KeyguardStateController mKeyguardStateController;
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
+ private boolean mWallpaperSupportsAmbientMode;
/**
* Injected constructor. See {@link KeyguardModule}.
@@ -2089,13 +2090,14 @@
int flags = 0;
if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock()
- || (mWakeAndUnlocking && !mPulsing)
- || isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) {
+ || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) {
flags |= WindowManagerPolicyConstants
.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
}
if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade()
- || (mWakeAndUnlocking && mPulsing)) {
+ || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) {
+ // When the wallpaper supports ambient mode, the scrim isn't fully opaque during
+ // wake and unlock and we should fade in the app on top of the wallpaper
flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2784,6 +2786,15 @@
mPulsing = pulsing;
}
+ /**
+ * Set if the wallpaper supports ambient mode. This is used to trigger the right animation.
+ * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
+ * with the light reveal scrim.
+ */
+ public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+ mWallpaperSupportsAmbientMode = supportsAmbientMode;
+ }
+
private static class StartKeyguardExitAnimParams {
@WindowManager.TransitionOldType int mTransit;
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index c1db8ed..9006417 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -16,6 +16,7 @@
package com.android.systemui.log
+import android.os.Trace
import android.util.Log
import com.android.systemui.log.dagger.LogModule
import java.io.PrintWriter
@@ -169,7 +170,7 @@
buffer.add(message as LogMessageImpl)
if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
- echoToLogcat(message)
+ echo(message)
}
}
@@ -219,7 +220,7 @@
pw.println(message.printer(message))
}
- private fun echoToLogcat(message: LogMessage) {
+ private fun echo(message: LogMessage) {
val strMessage = message.printer(message)
when (message.level) {
LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
@@ -229,6 +230,7 @@
LogLevel.ERROR -> Log.e(message.tag, strMessage)
LogLevel.WTF -> Log.wtf(message.tag, strMessage)
}
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", strMessage)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 2facf3d..c743fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -212,14 +212,32 @@
isSsReactivated: Boolean
) {
if (addOrUpdatePlayer(key, oldKey, data)) {
+ // Log card received if a new resumable media card is added
MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ false,
it.surfaceForSmartspaceLogging,
rank = MediaPlayerData.getMediaPlayerIndex(key))
}
}
+ if (isSsReactivated) {
+ // If resumable media is reactivated by headphone connection, update instance
+ // id for each card and log a receive event.
+ MediaPlayerData.players().forEachIndexed { index, it ->
+ if (it.recommendationViewHolder == null) {
+ it.mInstanceId = SmallHash.hash(it.mUid +
+ systemClock.currentTimeMillis().toInt())
+ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
+ it.mInstanceId,
+ it.mUid,
+ /* isRecommendationCard */ false,
+ it.surfaceForSmartspaceLogging,
+ rank = index)
+ }
+ }
+ }
if (mediaCarouselScrollHandler.visibleToUser &&
isSsReactivated && !mediaCarouselScrollHandler.qsExpanded) {
// It could happen that reactived media player isn't visible to user because
@@ -252,6 +270,7 @@
MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ true,
it.surfaceForSmartspaceLogging,
rank = MediaPlayerData.getMediaPlayerIndex(key))
@@ -261,6 +280,7 @@
MediaPlayerData.getMediaPlayerIndex(key)) {
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ true,
it.surfaceForSmartspaceLogging)
}
@@ -339,9 +359,9 @@
if (activeMediaIndex != -1) {
previousVisiblePlayerKey?.let {
val previousVisibleIndex = MediaPlayerData.playerKeys()
- .indexOfFirst { key -> it == key }
+ .indexOfFirst { key -> it == key }
mediaCarouselScrollHandler
- .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
+ .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
} ?: {
mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex)
}
@@ -355,11 +375,11 @@
MediaPlayerData.moveIfExists(oldKey, key)
val existingPlayer = MediaPlayerData.getMediaPlayer(key)
val curVisibleMediaKey = MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
var newPlayer = mediaControlPanelFactory.get()
newPlayer.attachPlayer(
- PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
+ PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
@@ -407,14 +427,14 @@
var newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
+ RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT)
+ ViewGroup.LayoutParams.WRAP_CONTENT)
newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
newRecs.bindRecommendation(data.copy(backgroundColor = bgColor))
val curVisibleMediaKey = MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
MediaPlayerData.addMediaRecommendation(key, data, newRecs, shouldPrioritize, systemClock)
updatePlayerToState(newRecs, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
@@ -462,7 +482,7 @@
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
smartspaceMediaData?.let {
addSmartspaceMediaRecommendations(
- it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
+ it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
}
} else {
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
@@ -585,7 +605,7 @@
?: endShowsActive
if (currentlyShowingOnlyActive != endShowsActive ||
((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
- startShowsActive != endShowsActive)) {
+ startShowsActive != endShowsActive)) {
// Whenever we're transitioning from between differing states or the endstate differs
// we reset the translation
currentlyShowingOnlyActive = endShowsActive
@@ -696,23 +716,43 @@
}
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
mediaControlPanel.mInstanceId,
+ mediaControlPanel.mUid,
isRecommendationCard,
mediaControlPanel.surfaceForSmartspaceLogging)
}
}
@JvmOverloads
+ /**
+ * Log Smartspace events
+ *
+ * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
+ * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
+ * instanceId
+ * @param uid uid for the application that media comes from
+ * @param isRecommendationCard whether the card is media recommendation
+ * @param surface which display surface the media card is on (e.g. lockscreen, shade)
+ * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
+ * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
+ * @param interactedSubcardCardinality how many media items were shown to the user when there
+ * is user interaction
+ * @param rank the rank for media card in the media carousel, starting from 0
+ *
+ */
fun logSmartspaceCardReported(
eventId: Int,
instanceId: Int,
+ uid: Int,
isRecommendationCard: Boolean,
surface: Int,
+ interactedSubcardRank: Int = 0,
+ interactedSubcardCardinality: Int = 0,
rank: Int = mediaCarouselScrollHandler.visibleMediaIndex
) {
// Only log media resume card when Smartspace data is available
if (!isRecommendationCard &&
- !mediaManager.smartspaceMediaData.isActive &&
- MediaPlayerData.smartspaceMediaData == null) {
+ !mediaManager.smartspaceMediaData.isActive &&
+ MediaPlayerData.smartspaceMediaData == null) {
return
}
@@ -720,13 +760,21 @@
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
eventId,
instanceId,
- if (isRecommendationCard)
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS
- else
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_RESUME_MEDIA,
+ // Deprecated, replaced with AiAi feature type so we don't need to create logging
+ // card type for each new feature.
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
surface,
rank,
- mediaContent.getChildCount())
+ mediaContent.getChildCount(),
+ if (isRecommendationCard)
+ 15 // MEDIA_RECOMMENDATION
+ else
+ 31, // MEDIA_RESUME
+ uid,
+ interactedSubcardRank,
+ interactedSubcardCardinality,
+ 0 // received_latency_millis
+ )
/* ktlint-disable max-line-length */
}
@@ -738,18 +786,20 @@
if (!recommendation.isEmpty()) {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
recommendation.get(0).mInstanceId,
+ recommendation.get(0).mUid,
true,
recommendation.get(0).surfaceForSmartspaceLogging,
- /* rank */-1)
+ rank = -1)
} else {
val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
if (MediaPlayerData.players().size > visibleMediaIndex) {
val player = MediaPlayerData.players().elementAt(visibleMediaIndex)
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
player.mInstanceId,
- false,
+ player.mUid,
+ false,
player.surfaceForSmartspaceLogging,
- /* rank */-1)
+ rank = -1)
}
}
mediaManager.onSwipeToDismiss()
@@ -768,7 +818,7 @@
@VisibleForTesting
internal object MediaPlayerData {
private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
- emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+ emptyList(), emptyList(), "INVALID", null, null, null, true, null)
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
@@ -776,18 +826,18 @@
private set
data class MediaSortKey(
- // Whether the item represents a Smartspace media recommendation.
+ // Whether the item represents a Smartspace media recommendation.
val isSsMediaRec: Boolean,
val data: MediaData,
val updateTime: Long = 0
)
private val comparator =
- compareByDescending<MediaSortKey> { it.data.isPlaying }
- .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
- .thenByDescending { it.data.isLocalSession }
- .thenByDescending { !it.data.resumption }
- .thenByDescending { it.updateTime }
+ compareByDescending<MediaSortKey> { it.data.isPlaying }
+ .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
+ .thenByDescending { it.data.isLocalSession }
+ .thenByDescending { !it.data.resumption }
+ .thenByDescending { it.updateTime }
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
@@ -808,7 +858,8 @@
) {
shouldPrioritizeSs = shouldPrioritize
removeMediaPlayer(key)
- val sortKey = MediaSortKey(isSsMediaRec = true, EMPTY, clock.currentTimeMillis())
+ val sortKey = MediaSortKey(/* isSsMediaRec= */ true,
+ EMPTY.copy(isPlaying = false), clock.currentTimeMillis())
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
smartspaceMediaData = data
@@ -888,4 +939,4 @@
}
return false
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 15a7083..e7445f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -33,6 +33,7 @@
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.os.Process;
import android.text.Layout;
import android.util.Log;
import android.view.View;
@@ -74,6 +75,8 @@
private static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
+ private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google"
+ + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity";
private static final String EXTRAS_SMARTSPACE_INTENT =
"com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
private static final int MEDIA_RECOMMENDATION_ITEMS_PER_ROW = 3;
@@ -111,6 +114,9 @@
private int mAlbumArtSize;
// Instance id for logging purpose.
protected int mInstanceId = -1;
+ // Uid for the media app.
+ protected int mUid = Process.INVALID_UID;
+ private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
@@ -248,7 +254,8 @@
openGuts();
return true;
} else {
- return false;
+ closeGuts();
+ return true;
}
});
mRecommendationViewHolder.getCancel().setOnClickListener(v -> {
@@ -266,7 +273,13 @@
}
mKey = key;
MediaSession.Token token = data.getToken();
- mInstanceId = SmallHash.hash(data.getPackageName());
+ PackageManager packageManager = mContext.getPackageManager();
+ try {
+ mUid = packageManager.getApplicationInfo(data.getPackageName(), 0 /* flags */).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to look up package name", e);
+ }
+ mInstanceId = SmallHash.hash(mUid);
mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
@@ -360,27 +373,16 @@
final MediaDeviceData device = data.getDevice();
final int seamlessId = mPlayerViewHolder.getSeamless().getId();
- final int seamlessFallbackId = mPlayerViewHolder.getSeamlessFallback().getId();
- final boolean showFallback = device != null && !device.getEnabled();
- final int seamlessFallbackVisibility = showFallback ? View.VISIBLE : View.GONE;
- mPlayerViewHolder.getSeamlessFallback().setVisibility(seamlessFallbackVisibility);
- expandedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility);
- collapsedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility);
- final int seamlessVisibility = showFallback ? View.GONE : View.VISIBLE;
- mPlayerViewHolder.getSeamless().setVisibility(seamlessVisibility);
- expandedSet.setVisibility(seamlessId, seamlessVisibility);
- collapsedSet.setVisibility(seamlessId, seamlessVisibility);
- final float seamlessAlpha = data.getResumption() ? DISABLED_ALPHA : 1.0f;
+ // Disable clicking on output switcher for invalid devices and resumption controls
+ final boolean seamlessDisabled = (device != null && !device.getEnabled())
+ || data.getResumption();
+ final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
expandedSet.setAlpha(seamlessId, seamlessAlpha);
collapsedSet.setAlpha(seamlessId, seamlessAlpha);
- // Disable clicking on output switcher for resumption controls.
- mPlayerViewHolder.getSeamless().setEnabled(!data.getResumption());
+ mPlayerViewHolder.getSeamless().setEnabled(!seamlessDisabled);
String deviceString = null;
- if (showFallback) {
- iconView.setImageDrawable(null);
- } else if (device != null) {
+ if (device != null && device.getEnabled()) {
Drawable icon = device.getIcon();
- iconView.setVisibility(View.VISIBLE);
if (icon instanceof AdaptiveIcon) {
AdaptiveIcon aIcon = (AdaptiveIcon) icon;
aIcon.setBackgroundColor(mBackgroundColor);
@@ -391,10 +393,9 @@
deviceString = device.getName();
} else {
// Reset to default
- Log.w(TAG, "device is null. Not binding output chip.");
- iconView.setVisibility(View.GONE);
- deviceString = mContext.getString(
- com.android.internal.R.string.ext_media_seamless_action);
+ Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip.");
+ iconView.setImageResource(R.drawable.ic_media_home_devices);
+ deviceString = mContext.getString(R.string.media_seamless_other_device);
}
deviceName.setText(deviceString);
seamlessView.setContentDescription(deviceString);
@@ -531,10 +532,11 @@
}
// Set up recommendation card's header.
- ApplicationInfo applicationInfo = null;
+ ApplicationInfo applicationInfo;
try {
applicationInfo = mContext.getPackageManager()
.getApplicationInfo(data.getPackageName(), 0 /* flags */);
+ mUid = applicationInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Fail to get media recommendation's app info", e);
return;
@@ -553,7 +555,8 @@
headerTitleText.setText(appLabel);
}
// Set up media rec card's tap action if applicable.
- setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction());
+ setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
+ /* interactedSubcardRank */ -1);
// Set up media rec card's accessibility label.
recommendationCard.setContentDescription(
mContext.getString(R.string.controls_media_smartspace_rec_description, appLabel));
@@ -567,7 +570,8 @@
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
int mediaRecommendationNum = Math.min(mediaRecommendationList.size(),
MEDIA_RECOMMENDATION_MAX_NUM);
- for (int itemIndex = 0, uiComponentIndex = 0;
+ int uiComponentIndex = 0;
+ for (int itemIndex = 0;
itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum;
itemIndex++) {
SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex);
@@ -582,7 +586,16 @@
// Set up the media item's click listener if applicable.
ViewGroup mediaCoverContainer = mediaCoverContainers.get(uiComponentIndex);
- setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation);
+ setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation,
+ uiComponentIndex);
+ // Bubble up the long-click event to the card.
+ mediaCoverContainer.setOnLongClickListener(v -> {
+ View parent = (View) v.getParent();
+ if (parent != null) {
+ parent.performLongClick();
+ }
+ return true;
+ });
// Set up the accessibility label for the media item.
String artistName = recommendation.getExtras()
@@ -614,10 +627,10 @@
mediaCoverItemsResIds.get(uiComponentIndex), true);
setVisibleAndAlpha(expandedSet,
mediaCoverContainersResIds.get(uiComponentIndex), true);
-
uiComponentIndex++;
}
+ mSmartspaceMediaItemsCount = uiComponentIndex;
// Set up long press to show guts setting panel.
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
@@ -625,6 +638,22 @@
closeGuts();
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
+
+ Intent dismissIntent = data.getDismissIntent();
+ if (dismissIntent == null) {
+ Log.w(TAG, "Cannot create dismiss action click action: "
+ + "extras missing dismiss_intent.");
+ return;
+ }
+
+ if (dismissIntent.getComponent() != null
+ && dismissIntent.getComponent().getClassName()
+ .equals(EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)) {
+ // Dismiss the card Smartspace data through Smartspace trampoline activity.
+ mContext.startActivity(dismissIntent);
+ } else {
+ mContext.sendBroadcast(dismissIntent);
+ }
});
mController = null;
@@ -750,7 +779,8 @@
private void setSmartspaceRecItemOnClickListener(
@NonNull View view,
- @NonNull SmartspaceAction action) {
+ @NonNull SmartspaceAction action,
+ int interactedSubcardRank) {
if (view == null || action == null || action.getIntent() == null
|| action.getIntent().getExtras() == null) {
Log.e(TAG, "No tap action can be set up");
@@ -758,9 +788,10 @@
}
view.setOnClickListener(v -> {
- // When media recommendation card is shown, it will always be the top card.
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
- /* isRecommendationCard */ true);
+ /* isRecommendationCard */ true,
+ interactedSubcardRank,
+ getSmartspaceSubCardCardinality());
if (shouldSmartspaceRecItemOpenInForeground(action)) {
// Request to unlock the device if the activity needs to be opened in foreground.
@@ -818,9 +849,28 @@
}
private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
+ logSmartspaceCardReported(eventId, isRecommendationCard,
+ /* interactedSubcardRank */ 0,
+ /* interactedSubcardCardinality */ 0);
+ }
+
+ private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard,
+ int interactedSubcardRank, int interactedSubcardCardinality) {
mMediaCarouselController.logSmartspaceCardReported(eventId,
mInstanceId,
+ mUid,
isRecommendationCard,
- getSurfaceForSmartspaceLogging());
+ getSurfaceForSmartspaceLogging(),
+ interactedSubcardRank,
+ interactedSubcardCardinality);
}
-}
+
+ private int getSmartspaceSubCardCardinality() {
+ if (!mMediaCarouselController.getMediaCarouselScrollHandler().getQsExpanded()
+ && mSmartspaceMediaItemsCount > 3) {
+ return 3;
+ }
+
+ return mSmartspaceMediaItemsCount;
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index c8deb01..79206e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.content.Context
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
@@ -32,6 +33,8 @@
private const val TAG = "MediaDataFilter"
private const val DEBUG = true
+private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = ("com.google" +
+ ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity")
private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds"
/**
@@ -51,6 +54,7 @@
* background users (e.g. timeouts).
*/
class MediaDataFilter @Inject constructor(
+ private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
private val mediaResumeListener: MediaResumeListener,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@@ -229,6 +233,18 @@
mediaDataManager.setTimedOut(it, timedOut = true, forceUpdate = true)
}
if (smartspaceMediaData.isActive) {
+ val dismissIntent = smartspaceMediaData.dismissIntent
+ if (dismissIntent == null) {
+ Log.w(TAG, "Cannot create dismiss action click action: " +
+ "extras missing dismiss_intent.")
+ } else if (dismissIntent.getComponent() != null &&
+ dismissIntent.getComponent().getClassName()
+ == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
+ // Dismiss the card Smartspace data through Smartspace trampoline activity.
+ context.startActivity(dismissIntent)
+ } else {
+ context.sendBroadcast(dismissIntent)
+ }
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 0a28b47..eacdab6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -76,12 +76,13 @@
private const val TAG = "MediaDataManager"
private const val DEBUG = true
+private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
- "INVALID", null, emptyList(), 0)
+ "INVALID", null, emptyList(), null, 0)
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
if (!sbn.notification.hasMediaSession()) {
@@ -212,8 +213,8 @@
mediaDataCombineLatest.addListener(mediaDataFilter)
// Set up links back into the pipeline for listeners that need to send events upstream.
- mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
- setTimedOut(token, timedOut) }
+ mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean ->
+ setTimedOut(key, timedOut) }
mediaResumeListener.setManager(this)
mediaDataFilter.mediaDataManager = this
@@ -414,14 +415,18 @@
* This will make the player not active anymore, hiding it from QQS and Keyguard.
* @see MediaData.active
*/
- internal fun setTimedOut(token: String, timedOut: Boolean, forceUpdate: Boolean = false) {
- mediaEntries[token]?.let {
+ internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) {
+ mediaEntries[key]?.let {
if (it.active == !timedOut && !forceUpdate) {
+ if (it.resumption) {
+ if (DEBUG) Log.d(TAG, "timing out resume player $key")
+ dismissMediaData(key, 0L /* delay */)
+ }
return
}
it.active = !timedOut
- if (DEBUG) Log.d(TAG, "Updating $token timedOut: $timedOut")
- onMediaDataLoaded(token, token, it)
+ if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut")
+ onMediaDataLoaded(key, key, it)
}
}
@@ -879,12 +884,22 @@
target: SmartspaceTarget,
isActive: Boolean
): SmartspaceMediaData {
+ var dismissIntent: Intent? = null
+ if (target.baseAction != null && target.baseAction.extras != null) {
+ dismissIntent = target
+ .baseAction
+ .extras
+ .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent
+ }
packageName(target)?.let {
return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it,
- target.baseAction, target.iconGrid, 0)
+ target.baseAction, target.iconGrid,
+ dismissIntent, 0)
}
return EMPTY_SMARTSPACE_MEDIA_DATA
- .copy(targetId = target.smartspaceTargetId, isActive = isActive)
+ .copy(targetId = target.smartspaceTargetId,
+ isActive = isActive,
+ dismissIntent = dismissIntent)
}
private fun packageName(target: SmartspaceTarget): String? {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index ab568c8..608c784 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -35,6 +35,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Utils
+import com.android.systemui.util.time.SystemClock
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.ConcurrentLinkedQueue
@@ -53,11 +54,13 @@
@Background private val backgroundExecutor: Executor,
private val tunerService: TunerService,
private val mediaBrowserFactory: ResumeMediaBrowserFactory,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ private val systemClock: SystemClock
) : MediaDataManager.Listener, Dumpable {
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
- private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
+ private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> =
+ ConcurrentLinkedQueue()
private lateinit var mediaDataManager: MediaDataManager
@@ -131,14 +134,32 @@
val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null)
val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())
?.dropLastWhile { it.isEmpty() }
+ var needsUpdate = false
components?.forEach {
val info = it.split("/")
val packageName = info[0]
val className = info[1]
val component = ComponentName(packageName, className)
- resumeComponents.add(component)
+
+ val lastPlayed = if (info.size == 3) {
+ try {
+ info[2].toLong()
+ } catch (e: NumberFormatException) {
+ needsUpdate = true
+ systemClock.currentTimeMillis()
+ }
+ } else {
+ needsUpdate = true
+ systemClock.currentTimeMillis()
+ }
+ resumeComponents.add(component to lastPlayed)
}
Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
+
+ if (needsUpdate) {
+ // Save any missing times that we had to fill in
+ writeSharedPrefs()
+ }
}
/**
@@ -149,9 +170,12 @@
return
}
+ val now = systemClock.currentTimeMillis()
resumeComponents.forEach {
- val browser = mediaBrowserFactory.create(mediaBrowserCallback, it)
- browser.findRecentMedia()
+ if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
+ val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first)
+ browser.findRecentMedia()
+ }
}
}
@@ -234,18 +258,24 @@
*/
private fun updateResumptionList(componentName: ComponentName) {
// Remove if exists
- resumeComponents.remove(componentName)
+ resumeComponents.remove(resumeComponents.find { it.first.equals(componentName) })
// Insert at front of queue
- resumeComponents.add(componentName)
+ val currentTime = systemClock.currentTimeMillis()
+ resumeComponents.add(componentName to currentTime)
// Remove old components if over the limit
if (resumeComponents.size > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) {
resumeComponents.remove()
}
- // Save changes
+ writeSharedPrefs()
+ }
+
+ private fun writeSharedPrefs() {
val sb = StringBuilder()
resumeComponents.forEach {
- sb.append(it.flattenToString())
+ sb.append(it.first.flattenToString())
+ sb.append("/")
+ sb.append(it.second)
sb.append(ResumeMediaBrowser.DELIMITER)
}
val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9a39193..6f04771 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -20,6 +20,7 @@
import android.media.session.PlaybackState
import android.os.SystemProperties
import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -29,9 +30,15 @@
private const val DEBUG = true
private const val TAG = "MediaTimeout"
-private val PAUSED_MEDIA_TIMEOUT = SystemProperties
+
+@VisibleForTesting
+val PAUSED_MEDIA_TIMEOUT = SystemProperties
.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
+@VisibleForTesting
+val RESUME_MEDIA_TIMEOUT = SystemProperties
+ .getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
+
/**
* Controller responsible for keeping track of playback states and expiring inactive streams.
*/
@@ -45,8 +52,9 @@
/**
* Callback representing that a media object is now expired:
- * @param token Media session unique identifier
- * @param pauseTimeout True when expired for {@code PAUSED_MEDIA_TIMEOUT}
+ * @param key Media control unique identifier
+ * @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media,
+ * or {@code RESUME_MEDIA_TIMEOUT} for resume media
*/
lateinit var timeoutCallback: (String, Boolean) -> Unit
@@ -122,6 +130,7 @@
var timedOut = false
var playing: Boolean? = null
+ var resumption: Boolean? = null
var destroyed = false
var mediaData: MediaData = data
@@ -159,12 +168,19 @@
}
override fun onSessionDestroyed() {
- // If the session is destroyed, the controller is no longer valid, and we will need to
- // recreate it if this key is updated later
if (DEBUG) {
Log.d(TAG, "Session destroyed for $key")
}
- destroy()
+
+ if (resumption == true) {
+ // Some apps create a session when MBS is queried. We should unregister the
+ // controller since it will no longer be valid, but don't cancel the timeout
+ mediaController?.unregisterCallback(this)
+ } else {
+ // For active controls, if the session is destroyed, clean up everything since we
+ // will need to recreate it if this key is updated later
+ destroy()
+ }
}
private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
@@ -173,20 +189,28 @@
}
val isPlaying = state != null && isPlayingState(state.state)
- if (playing == isPlaying && playing != null) {
+ val resumptionChanged = resumption != mediaData.resumption
+ if (playing == isPlaying && playing != null && !resumptionChanged) {
return
}
playing = isPlaying
+ resumption = mediaData.resumption
if (!isPlaying) {
if (DEBUG) {
- Log.v(TAG, "schedule timeout for $key")
+ Log.v(TAG, "schedule timeout for $key playing $isPlaying, $resumption")
}
- if (cancellation != null) {
+ if (cancellation != null && !resumptionChanged) {
+ // if the media changed resume state, we'll need to adjust the timeout length
if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.")
return
}
- expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state")
+ expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
+ val timeout = if (mediaData.resumption) {
+ RESUME_MEDIA_TIMEOUT
+ } else {
+ PAUSED_MEDIA_TIMEOUT
+ }
cancellation = mainExecutor.executeDelayed({
cancellation = null
if (DEBUG) {
@@ -195,7 +219,7 @@
timedOut = true
// this event is async, so it's safe even when `dispatchEvents` is false
timeoutCallback(key, timedOut)
- }, PAUSED_MEDIA_TIMEOUT)
+ }, timeout)
} else {
expireMediaTimeout(key, "playback started - $state, $key")
timedOut = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 791f59d..35603b6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -43,7 +43,6 @@
val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
- val seamlessFallback = itemView.requireViewById<ImageView>(R.id.media_seamless_fallback)
// Seek bar
val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
@@ -124,7 +123,6 @@
R.id.header_title,
R.id.header_artist,
R.id.media_seamless,
- R.id.media_seamless_fallback,
R.id.notification_media_progress_time,
R.id.media_progress_bar,
R.id.action0,
diff --git a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
index 142628c..c0f79d5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
@@ -21,7 +21,6 @@
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
-import androidx.annotation.IntegerRes
import com.android.systemui.R
import com.android.systemui.util.animation.TransitionLayout
@@ -47,14 +46,14 @@
itemView.requireViewById(R.id.media_cover4_container),
itemView.requireViewById(R.id.media_cover5_container),
itemView.requireViewById(R.id.media_cover6_container))
- val mediaCoverItemsResIds = listOf<@IntegerRes Int>(
+ val mediaCoverItemsResIds = listOf<Int>(
R.id.media_cover1,
R.id.media_cover2,
R.id.media_cover3,
R.id.media_cover4,
R.id.media_cover5,
R.id.media_cover6)
- val mediaCoverContainersResIds = listOf<@IntegerRes Int>(
+ val mediaCoverContainersResIds = listOf<Int>(
R.id.media_cover1_container,
R.id.media_cover2_container,
R.id.media_cover3_container,
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
index 9ac1289..61fdefd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
+import android.content.Intent
/** State of a Smartspace media recommendations view. */
data class SmartspaceMediaData(
@@ -45,6 +46,10 @@
*/
val recommendations: List<SmartspaceAction>,
/**
+ * Intent for the user's initiated dismissal.
+ */
+ val dismissIntent: Intent?,
+ /**
* View's background color.
*/
val backgroundColor: Int
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
index b6c2ef1..140a1fe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
@@ -1,10 +1,13 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceTarget
+import android.util.Log
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import javax.inject.Inject
+private const val TAG = "SsMediaDataProvider"
+
/** Provides SmartspaceTargets of media types for SystemUI media control. */
class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin {
@@ -31,6 +34,10 @@
}
}
+ if (!mediaTargets.isEmpty()) {
+ Log.d(TAG, "Forwarding Smartspace media updates $mediaTargets")
+ }
+
smartspaceMediaTargets = mediaTargets
smartspaceMediaTargetListeners.forEach {
it.onSmartspaceTargetsUpdated(smartspaceMediaTargets)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index cb11454..8e6eb02 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -410,6 +410,12 @@
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
+ // TODO(b/198002034): Content observers currently can still be called back after being
+ // unregistered, and in this case we can ignore the change if the nav bar has been
+ // destroyed already
+ if (mNavigationBarView == null) {
+ return;
+ }
updateAssistantEntrypoints();
}
};
@@ -601,6 +607,7 @@
}
public void destroyView() {
+ setAutoHideController(/* autoHideController */ null);
mCommandQueue.removeCallback(this);
mContext.getSystemService(WindowManager.class).removeViewImmediate(
mNavigationBarView.getRootView());
@@ -651,7 +658,7 @@
if (mIsOnDefaultDisplay) {
final RotationButtonController rotationButtonController =
mNavigationBarView.getRotationButtonController();
- rotationButtonController.addRotationCallback(mRotationWatcher);
+ rotationButtonController.setRotationCallback(mRotationWatcher);
// Reset user rotation pref to match that of the WindowManager if starting in locked
// mode. This will automatically happen when switching from auto-rotate to locked mode.
@@ -690,6 +697,9 @@
@Override
public void onViewDetachedFromWindow(View v) {
+ final RotationButtonController rotationButtonController =
+ mNavigationBarView.getRotationButtonController();
+ rotationButtonController.setRotationCallback(null);
mNavigationBarView.getBarTransitions().destroy();
mNavigationBarView.getLightTransitionsController().destroy(mContext);
mOverviewProxyService.removeCallback(mOverviewProxyListener);
@@ -937,6 +947,11 @@
@Override
public void onRotationProposal(final int rotation, boolean isValid) {
+ // The CommandQueue callbacks are added when the view is created to ensure we track other
+ // states, but until the view is attached (at the next traversal), the view's display is
+ // not valid. Just ignore the rotation in this case.
+ if (!mNavigationBarView.isAttachedToWindow()) return;
+
final int winRotation = mNavigationBarView.getDisplay().getRotation();
final boolean rotateSuggestionsDisabled = RotationButtonController
.hasDisable2RotateSuggestionFlag(mDisabledFlags2);
@@ -1516,7 +1531,7 @@
}
/** Sets {@link AutoHideController} to the navigation bar. */
- public void setAutoHideController(AutoHideController autoHideController) {
+ private void setAutoHideController(AutoHideController autoHideController) {
mAutoHideController = autoHideController;
if (mAutoHideController != null) {
mAutoHideController.setNavigationBar(mAutoHideUiElement);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index b9e9240..1628c71 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -395,7 +395,6 @@
void removeNavigationBar(int displayId) {
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
- navBar.setAutoHideController(/* autoHideController */ null);
navBar.destroyView();
mNavigationBars.remove(displayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 61e8033..70c21e4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -101,10 +101,7 @@
public class NavigationBarView extends FrameLayout implements
NavigationModeController.ModeChangedListener {
final static boolean DEBUG = false;
- final static String TAG = "StatusBar/NavBarView";
-
- // slippery nav bar when everything is disabled, e.g. during setup
- final static boolean SLIPPERY_WHEN_DISABLED = true;
+ final static String TAG = "NavBarView";
final static boolean ALTERNATE_CAR_MODE_UI = false;
private final RegionSamplingHelper mRegionSamplingHelper;
@@ -274,7 +271,7 @@
};
private final Consumer<Boolean> mRotationButtonListener = (visible) -> {
- if (visible) {
+ if (visible && mAutoHideController != null) {
// If the button will actually become visible and the navbar is about to hide,
// tell the statusbar to keep it around for longer
mAutoHideController.touchAutoHide();
@@ -373,6 +370,12 @@
}
}
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ Log.e(TAG, "onSetAlpha", new Throwable());
+ return super.onSetAlpha(alpha);
+ }
+
public void setAutoHideController(AutoHideController autoHideController) {
mAutoHideController = autoHideController;
}
@@ -1313,6 +1316,7 @@
dumpButton(pw, "back", getBackButton());
dumpButton(pw, "home", getHomeButton());
+ dumpButton(pw, "handle", getHomeHandle());
dumpButton(pw, "rcnt", getRecentsButton());
dumpButton(pw, "rota", getRotateSuggestionButton());
dumpButton(pw, "a11y", getAccessibilityButton());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 649ac87..8ea9ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -180,7 +180,7 @@
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
- void addRotationCallback(Consumer<Integer> watcher) {
+ void setRotationCallback(Consumer<Integer> watcher) {
mRotWatcherListener = watcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index a626681..d0aa710 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -29,6 +29,7 @@
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -67,6 +68,7 @@
private val privacyLogger: PrivacyLogger,
private val keyguardStateController: KeyguardStateController,
private val appOpsController: AppOpsController,
+ private val uiEventLogger: UiEventLogger,
@VisibleForTesting private val dialogProvider: DialogProvider
) {
@@ -81,7 +83,8 @@
@Main uiExecutor: Executor,
privacyLogger: PrivacyLogger,
keyguardStateController: KeyguardStateController,
- appOpsController: AppOpsController
+ appOpsController: AppOpsController,
+ uiEventLogger: UiEventLogger
) : this(
permissionManager,
packageManager,
@@ -93,6 +96,7 @@
privacyLogger,
keyguardStateController,
appOpsController,
+ uiEventLogger,
defaultDialogProvider
)
@@ -105,6 +109,7 @@
private val onDialogDismissed = object : PrivacyDialog.OnDialogDismissed {
override fun onDialogDismissed() {
privacyLogger.logPrivacyDialogDismissed()
+ uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
dialog = null
}
}
@@ -114,6 +119,8 @@
val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
+ uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+ userId, packageName)
privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
if (!keyguardStateController.isUnlocked) {
// If we are locked, hide the dialog so the user can unlock
@@ -155,7 +162,7 @@
val items = usage.mapNotNull {
val type = filterType(permGroupToPrivacyType(it.permGroupName))
val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
- userInfo?.let { ui ->
+ if (userInfo != null || it.isPhoneCall) {
type?.let { t ->
// Only try to get the app name if we actually need it
val appName = if (it.isPhoneCall) {
@@ -171,10 +178,14 @@
it.attribution,
it.lastAccess,
it.isActive,
- ui.isManagedProfile,
+ // If there's no user info, we're in a phoneCall in secondary user
+ userInfo?.isManagedProfile ?: false,
it.isPhoneCall
)
}
+ } else {
+ // No matching user or phone call
+ null
}
}
uiExecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
new file mode 100644
index 0000000..3ecc5a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.")
+ PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904),
+
+ @UiEvent(doc = "Privacy dialog is dismissed by user.")
+ PRIVACY_DIALOG_DISMISSED(905);
+
+ override fun getId() = _id
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
deleted file mode 100644
index 38b20ee..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.statusbar.phone.AutoTileManager.HOTSPOT;
-import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION;
-import static com.android.systemui.statusbar.phone.AutoTileManager.NIGHT;
-import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
-import com.android.systemui.util.UserAwareController;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-import javax.inject.Inject;
-
-public class AutoAddTracker implements UserAwareController {
-
- private static final String[][] CONVERT_PREFS = {
- {Key.QS_HOTSPOT_ADDED, HOTSPOT},
- {Key.QS_DATA_SAVER_ADDED, SAVER},
- {Key.QS_INVERT_COLORS_ADDED, INVERSION},
- {Key.QS_WORK_ADDED, WORK},
- {Key.QS_NIGHTDISPLAY_ADDED, NIGHT},
- };
-
- private final ArraySet<String> mAutoAdded;
- private final Context mContext;
- private int mUserId;
-
- public AutoAddTracker(Context context, int userId) {
- mContext = context;
- mUserId = userId;
- mAutoAdded = new ArraySet<>(getAdded());
- }
-
- /**
- * Init method must be called after construction to start listening
- */
- public void initialize() {
- // TODO: remove migration code and shared preferences keys after P release
- if (mUserId == UserHandle.USER_SYSTEM) {
- for (String[] convertPref : CONVERT_PREFS) {
- if (Prefs.getBoolean(mContext, convertPref[0], false)) {
- setTileAdded(convertPref[1]);
- Prefs.remove(mContext, convertPref[0]);
- }
- }
- }
- mContext.getContentResolver().registerContentObserver(
- Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver,
- UserHandle.USER_ALL);
- }
-
- @Override
- public void changeUser(UserHandle newUser) {
- if (newUser.getIdentifier() == mUserId) {
- return;
- }
- mUserId = newUser.getIdentifier();
- mAutoAdded.clear();
- mAutoAdded.addAll(getAdded());
- }
-
- @Override
- public int getCurrentUserId() {
- return mUserId;
- }
-
- public boolean isAdded(String tile) {
- return mAutoAdded.contains(tile);
- }
-
- public void setTileAdded(String tile) {
- if (mAutoAdded.add(tile)) {
- saveTiles();
- }
- }
-
- public void setTileRemoved(String tile) {
- if (mAutoAdded.remove(tile)) {
- saveTiles();
- }
- }
-
- public void destroy() {
- mContext.getContentResolver().unregisterContentObserver(mObserver);
- }
-
- private void saveTiles() {
- Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES,
- TextUtils.join(",", mAutoAdded), mUserId);
- }
-
- private Collection<String> getAdded() {
- String current = Secure.getStringForUser(mContext.getContentResolver(),
- Secure.QS_AUTO_ADDED_TILES, mUserId);
- if (current == null) {
- return Collections.emptyList();
- }
- return Arrays.asList(current.split(","));
- }
-
- @VisibleForTesting
- protected final ContentObserver mObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- mAutoAdded.clear();
- mAutoAdded.addAll(getAdded());
- }
- };
-
- public static class Builder {
- private final Context mContext;
- private int mUserId;
-
- @Inject
- public Builder(Context context) {
- mContext = context;
- }
-
- public Builder setUserId(int userId) {
- mUserId = userId;
- return this;
- }
-
- public AutoAddTracker build() {
- return new AutoAddTracker(mContext, mUserId);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
new file mode 100644
index 0000000..7ffa9d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.text.TextUtils
+import android.util.ArraySet
+import android.util.Log
+import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.UserAwareController
+import com.android.systemui.util.settings.SecureSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "AutoAddTracker"
+
+/**
+ * Class to track tiles that have been auto-added
+ *
+ * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES].
+ *
+ * It also handles restore gracefully.
+ */
+class AutoAddTracker @VisibleForTesting constructor(
+ private val secureSettings: SecureSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val qsHost: QSHost,
+ private val dumpManager: DumpManager,
+ private val mainHandler: Handler?,
+ private val backgroundExecutor: Executor,
+ private var userId: Int
+) : UserAwareController, Dumpable {
+
+ companion object {
+ private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
+ }
+
+ @GuardedBy("autoAdded")
+ private val autoAdded = ArraySet<String>()
+ private var restoredTiles: Set<String>? = null
+
+ override val currentUserId: Int
+ get() = userId
+
+ private val contentObserver = object : ContentObserver(mainHandler) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: Collection<Uri>,
+ flags: Int,
+ _userId: Int
+ ) {
+ if (_userId != userId) {
+ // Ignore changes outside of our user. We'll load the correct value on user change
+ return
+ }
+ loadTiles()
+ }
+ }
+
+ private val restoreReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != Intent.ACTION_SETTING_RESTORED) return
+ processRestoreIntent(intent)
+ }
+ }
+
+ private fun processRestoreIntent(intent: Intent) {
+ when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
+ Settings.Secure.QS_TILES -> {
+ restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+ ?.split(",")
+ ?.toSet()
+ ?: run {
+ Log.w(TAG, "Null restored tiles for user $userId")
+ emptySet()
+ }
+ }
+ Settings.Secure.QS_AUTO_ADDED_TILES -> {
+ restoredTiles?.let { tiles ->
+ val restoredAutoAdded = intent
+ .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+ ?.split(",")
+ ?: emptyList()
+ val autoAddedBeforeRestore = intent
+ .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)
+ ?.split(",")
+ ?: emptyList()
+
+ val tilesToRemove = restoredAutoAdded.filter { it !in tiles }
+ if (tilesToRemove.isNotEmpty()) {
+ qsHost.removeTiles(tilesToRemove)
+ }
+ val tiles = synchronized(autoAdded) {
+ autoAdded.clear()
+ autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
+ getTilesFromListLocked()
+ }
+ saveTiles(tiles)
+ } ?: run {
+ Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
+ "${Settings.Secure.QS_TILES} for user $userId")
+ }
+ }
+ else -> {} // Do nothing for other Settings
+ }
+ }
+
+ /**
+ * Init method must be called after construction to start listening
+ */
+ fun initialize() {
+ dumpManager.registerDumpable(TAG, this)
+ loadTiles()
+ secureSettings.registerContentObserverForUser(
+ secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
+ contentObserver,
+ UserHandle.USER_ALL
+ )
+ registerBroadcastReceiver()
+ }
+
+ /**
+ * Unregister listeners, receivers and observers
+ */
+ fun destroy() {
+ dumpManager.unregisterDumpable(TAG)
+ secureSettings.unregisterContentObserver(contentObserver)
+ unregisterBroadcastReceiver()
+ }
+
+ private fun registerBroadcastReceiver() {
+ broadcastDispatcher.registerReceiver(
+ restoreReceiver,
+ FILTER,
+ backgroundExecutor,
+ UserHandle.of(userId)
+ )
+ }
+
+ private fun unregisterBroadcastReceiver() {
+ broadcastDispatcher.unregisterReceiver(restoreReceiver)
+ }
+
+ override fun changeUser(newUser: UserHandle) {
+ if (newUser.identifier == userId) return
+ unregisterBroadcastReceiver()
+ userId = newUser.identifier
+ restoredTiles = null
+ loadTiles()
+ registerBroadcastReceiver()
+ }
+
+ /**
+ * Returns `true` if the tile has been auto-added before
+ */
+ fun isAdded(tile: String): Boolean {
+ return synchronized(autoAdded) {
+ tile in autoAdded
+ }
+ }
+
+ /**
+ * Sets a tile as auto-added.
+ *
+ * From here on, [isAdded] will return true for that tile.
+ */
+ fun setTileAdded(tile: String) {
+ val tiles = synchronized(autoAdded) {
+ if (autoAdded.add(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
+ }
+ }
+ tiles?.let { saveTiles(it) }
+ }
+
+ /**
+ * Removes a tile from the list of auto-added.
+ *
+ * This allows for this tile to be auto-added again in the future.
+ */
+ fun setTileRemoved(tile: String) {
+ val tiles = synchronized(autoAdded) {
+ if (autoAdded.remove(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
+ }
+ }
+ tiles?.let { saveTiles(it) }
+ }
+
+ private fun getTilesFromListLocked(): String {
+ return TextUtils.join(",", autoAdded)
+ }
+
+ private fun saveTiles(tiles: String) {
+ secureSettings.putStringForUser(
+ Settings.Secure.QS_AUTO_ADDED_TILES,
+ tiles,
+ /* tag */ null,
+ /* makeDefault */ false,
+ userId,
+ /* overrideableByRestore */ true
+ )
+ }
+
+ private fun loadTiles() {
+ synchronized(autoAdded) {
+ autoAdded.clear()
+ autoAdded.addAll(getAdded())
+ }
+ }
+
+ private fun getAdded(): Collection<String> {
+ val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
+ return current?.split(",") ?: emptySet()
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("Current user: $userId")
+ pw.println("Added tiles: $autoAdded")
+ }
+
+ @SysUISingleton
+ class Builder @Inject constructor(
+ private val secureSettings: SecureSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val qsHost: QSHost,
+ private val dumpManager: DumpManager,
+ @Main private val handler: Handler,
+ @Background private val executor: Executor
+ ) {
+ private var userId: Int = 0
+
+ fun setUserId(_userId: Int): Builder {
+ userId = _userId
+ return this
+ }
+
+ fun build(): AutoAddTracker {
+ return AutoAddTracker(
+ secureSettings,
+ broadcastDispatcher,
+ qsHost,
+ dumpManager,
+ handler,
+ executor,
+ userId
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 59e5eb8..6f12e46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,6 +23,7 @@
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
+import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
@@ -289,6 +290,16 @@
}
}
+ @Override
+ protected boolean isTransformedTouchPointInView(float x, float y,
+ View child, PointF outLocalPoint) {
+ // Prevent touches outside the clipped area from propagating to a child in that area.
+ if (mClippingEnabled && y + getTranslationY() > mFancyClippingTop) {
+ return false;
+ }
+ return super.isTransformedTouchPointInView(x, y, child, outLocalPoint);
+ }
+
private void updateClippingPath() {
mFancyClippingPath.reset();
if (!mClippingEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 000fd1c..9f585bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -37,6 +37,7 @@
void removeCallback(Callback callback);
TileServices getTileServices();
void removeTile(String tileSpec);
+ void removeTiles(Collection<String> specs);
void unmarkTileAsAutoAdded(String tileSpec);
int indexOf(String tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 4739a3f..08cb4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.metrics.LogMaker;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
@@ -80,7 +81,8 @@
private final QSHost.Callback mQSHostCallback = this::setTiles;
- private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
+ @VisibleForTesting
+ protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
@@ -156,6 +158,7 @@
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
mHost.addCallback(mQSHostCallback);
setTiles();
+ mLastOrientation = getResources().getConfiguration().orientation;
switchTileLayout(true);
mDumpManager.registerDumpable(mView.getDumpableTag(), this);
@@ -356,8 +359,7 @@
return false;
}
return mUsingMediaPlayer && mMediaHost.getVisible()
- && getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
}
private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 4a75810..e60fb49 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -351,6 +351,17 @@
changeTileSpecs(tileSpecs-> tileSpecs.remove(spec));
}
+ /**
+ * Remove many tiles at once.
+ *
+ * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called
+ * multiple times).
+ */
+ @Override
+ public void removeTiles(Collection<String> specs) {
+ changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs));
+ }
+
@Override
public void unmarkTileAsAutoAdded(String spec) {
if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
@@ -372,6 +383,7 @@
* @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
*/
public void addTile(String spec, int requestPosition) {
+ if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile");
changeTileSpecs(tileSpecs -> {
if (tileSpecs.contains(spec)) return false;
@@ -386,6 +398,7 @@
}
void saveTilesToSettings(List<String> tileSpecs) {
+ if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile");
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 77906ab..84b961e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.VariableDateView;
import java.util.List;
@@ -62,11 +63,14 @@
protected QuickQSPanel mHeaderQsPanel;
private View mDatePrivacyView;
private View mDateView;
+ // DateView next to clock. Visible on QQS
+ private VariableDateView mClockDateView;
private View mSecurityHeaderView;
private View mClockIconsView;
private View mContainer;
private View mQSCarriers;
+ private ViewGroup mClockContainer;
private Clock mClockView;
private Space mDatePrivacySeparator;
private View mClockIconsSeparator;
@@ -86,7 +90,6 @@
private int mWaterfallTopInset;
private int mCutOutPaddingLeft;
private int mCutOutPaddingRight;
- private float mViewAlpha = 1.0f;
private float mKeyguardExpansionFraction;
private int mTextColorPrimary = Color.TRANSPARENT;
private int mTopViewMeasureHeight;
@@ -123,18 +126,24 @@
mIconContainer = findViewById(R.id.statusIcons);
mPrivacyChip = findViewById(R.id.privacy_chip);
mDateView = findViewById(R.id.date);
+ mClockDateView = findViewById(R.id.date_clock);
mSecurityHeaderView = findViewById(R.id.header_text_container);
mClockIconsSeparator = findViewById(R.id.separator);
mRightLayout = findViewById(R.id.rightLayout);
mDateContainer = findViewById(R.id.date_container);
mPrivacyContainer = findViewById(R.id.privacy_container);
+ mClockContainer = findViewById(R.id.clock_container);
mClockView = findViewById(R.id.clock);
mDatePrivacySeparator = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
updateResources();
+ Configuration config = mContext.getResources().getConfiguration();
+ setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ setSecurityHeaderContainerVisibility(
+ config.orientation == Configuration.ORIENTATION_LANDSCAPE);
// Don't need to worry about tuner settings for this icon
mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
@@ -177,7 +186,7 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) {
mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight();
- updateAnimators();
+ post(this::updateAnimators);
}
}
@@ -186,6 +195,8 @@
super.onConfigurationChanged(newConfig);
updateResources();
setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ setSecurityHeaderContainerVisibility(
+ newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
@Override
@@ -206,6 +217,10 @@
mPrivacyContainer.setLayoutParams(lp);
}
+ private void setSecurityHeaderContainerVisibility(boolean landscape) {
+ mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE);
+ }
+
private void updateBatteryMode() {
if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
@@ -262,6 +277,25 @@
updateBatteryMode();
updateHeadersPadding();
updateAnimators();
+
+ updateClockDatePadding();
+ }
+
+ private void updateClockDatePadding() {
+ int startPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding);
+ int endPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding);
+ mClockView.setPaddingRelative(
+ startPadding,
+ mClockView.getPaddingTop(),
+ endPadding,
+ mClockView.getPaddingBottom()
+ );
+
+ MarginLayoutParams lp = (MarginLayoutParams) mClockDateView.getLayoutParams();
+ lp.setMarginStart(endPadding);
+ mClockDateView.setLayoutParams(lp);
}
private void updateAnimators() {
@@ -280,7 +314,8 @@
TouchAnimator.Builder builder = new TouchAnimator.Builder()
.addFloat(mSecurityHeaderView, "alpha", 0, 1)
// These views appear on expanding down
- .addFloat(mClockView, "alpha", 0, 1)
+ .addFloat(mDateView, "alpha", 0, 0, 1)
+ .addFloat(mClockDateView, "alpha", 1, 0, 0)
.addFloat(mQSCarriers, "alpha", 0, 1)
.setListener(new TouchAnimator.ListenerAdapter() {
@Override
@@ -289,10 +324,14 @@
if (!mIsSingleCarrier) {
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
}
+ // Make it gone so there's enough room for carrier names
+ mClockDateView.setVisibility(View.GONE);
}
@Override
public void onAnimationStarted() {
+ mClockDateView.setVisibility(View.VISIBLE);
+ mClockDateView.setFreezeSwitching(true);
setSeparatorVisibility(false);
if (!mIsSingleCarrier) {
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
@@ -302,6 +341,8 @@
@Override
public void onAnimationAtStart() {
super.onAnimationAtStart();
+ mClockDateView.setFreezeSwitching(false);
+ mClockDateView.setVisibility(View.VISIBLE);
setSeparatorVisibility(mShowClockIconsSeparator);
// In QQS we never ignore RSSI.
mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots);
@@ -434,10 +475,11 @@
mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE);
mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams();
+ LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) mClockContainer.getLayoutParams();
lp.width = visible ? 0 : WRAP_CONTENT;
lp.weight = visible ? 1f : 0f;
- mClockView.setLayoutParams(lp);
+ mClockContainer.setLayoutParams(lp);
lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams();
lp.width = visible ? 0 : WRAP_CONTENT;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index da75c9e..18d6e64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.VariableDateViewController;
import com.android.systemui.util.ViewController;
import java.util.List;
@@ -71,6 +72,9 @@
private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
private final FeatureFlags mFeatureFlags;
+ private final VariableDateViewController mVariableDateViewControllerDateView;
+ private final VariableDateViewController mVariableDateViewControllerClockDateView;
+
private boolean mListening;
private boolean mMicCameraIndicatorsEnabled;
private boolean mLocationIndicatorsEnabled;
@@ -134,7 +138,8 @@
SysuiColorExtractor colorExtractor,
PrivacyDialogController privacyDialogController,
QSExpansionPathInterpolator qsExpansionPathInterpolator,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ VariableDateViewController.Factory variableDateViewControllerFactory) {
super(view);
mPrivacyItemController = privacyItemController;
mActivityStarter = activityStarter;
@@ -154,6 +159,12 @@
mPrivacyChip = mView.findViewById(R.id.privacy_chip);
mClockView = mView.findViewById(R.id.clock);
mIconContainer = mView.findViewById(R.id.statusIcons);
+ mVariableDateViewControllerDateView = variableDateViewControllerFactory.create(
+ mView.requireViewById(R.id.date)
+ );
+ mVariableDateViewControllerClockDateView = variableDateViewControllerFactory.create(
+ mView.requireViewById(R.id.date_clock)
+ );
mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
@@ -205,6 +216,9 @@
mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots);
mDemoModeController.addCallback(mDemoModeReceiver);
+
+ mVariableDateViewControllerDateView.init();
+ mVariableDateViewControllerClockDateView.init();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 2e771d6..b1cd03c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -116,6 +116,9 @@
: icon.getInvisibleDrawable(mContext) : null;
int padding = icon != null ? icon.getPadding() : 0;
if (d != null) {
+ if (d.getConstantState() != null) {
+ d = d.getConstantState().newDrawable();
+ }
d.setAutoMirrored(false);
d.setLayoutDirection(getLayoutDirection());
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 73d1370..14e0f70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -54,7 +54,7 @@
private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null
private val icon = ResourceIcon.get(R.drawable.ic_alarm)
@VisibleForTesting
- internal val defaultIntent = Intent(AlarmClock.ACTION_SET_ALARM)
+ internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
lastAlarmInfo = nextAlarm
refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 4b13015..04f089d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -150,11 +150,7 @@
}
List<CastDevice> activeDevices = getActiveDevices();
- // We want to pop up the media route selection dialog if we either have no active devices
- // (neither routes nor projection), or if we have an active route. In other cases, we assume
- // that a projection is active. This is messy, but this tile never correctly handled the
- // case where multiple devices were active :-/.
- if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) {
+ if (willPopDetail()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
showDetail(true);
});
@@ -163,6 +159,15 @@
}
}
+ // We want to pop up the media route selection dialog if we either have no active devices
+ // (neither routes nor projection), or if we have an active route. In other cases, we assume
+ // that a projection is active. This is messy, but this tile never correctly handled the
+ // case where multiple devices were active :-/.
+ private boolean willPopDetail() {
+ List<CastDevice> activeDevices = getActiveDevices();
+ return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo);
+ }
+
private List<CastDevice> getActiveDevices() {
ArrayList<CastDevice> activeDevices = new ArrayList<>();
for (CastDevice device : mController.getCastDevices()) {
@@ -234,10 +239,12 @@
state.contentDescription = state.contentDescription + ","
+ mContext.getString(R.string.accessibility_quick_settings_open_details);
state.expandedAccessibilityClassName = Button.class.getName();
+ state.forceExpandIcon = willPopDetail();
} else {
state.state = Tile.STATE_UNAVAILABLE;
String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
state.secondaryLabel = noWifi;
+ state.forceExpandIcon = false;
}
state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
mDetailAdapter.updateItems(devices);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 34f2b63..87edc2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -47,6 +47,7 @@
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTileImpl<BooleanState> {
+
private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
private final HotspotController mHotspotController;
@@ -98,7 +99,7 @@
@Override
public Intent getLongClickIntent() {
- return new Intent(Settings.ACTION_TETHER_SETTINGS);
+ return new Intent(Settings.ACTION_WIFI_TETHER_SETTING);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 7cb1421..cc9e748 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,7 +51,9 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -66,15 +68,16 @@
/** Quick settings tile: Internet **/
public class InternetTile extends QSTileImpl<SignalState> {
private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
- private static final Intent INTERNET_PANEL =
- new Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
protected final NetworkController mController;
+ private final AccessPointController mAccessPointController;
private final DataUsageController mDataController;
// The last updated tile state, 0: mobile, 1: wifi, 2: ethernet.
private int mLastTileState = -1;
protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback();
+ private final InternetDialogFactory mInternetDialogFactory;
+ final Handler mHandler;
@Inject
public InternetTile(
@@ -86,11 +89,16 @@
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- NetworkController networkController
+ NetworkController networkController,
+ AccessPointController accessPointController,
+ InternetDialogFactory internetDialogFactory
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
+ mInternetDialogFactory = internetDialogFactory;
+ mHandler = mainHandler;
mController = networkController;
+ mAccessPointController = accessPointController;
mDataController = mController.getMobileDataController();
mController.observe(getLifecycle(), mSignalCallback);
}
@@ -114,7 +122,9 @@
@Override
protected void handleClick(@Nullable View view) {
- mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0);
+ mHandler.post(() -> mInternetDialogFactory.create(true,
+ mAccessPointController.canConfigMobileData(),
+ mAccessPointController.canConfigWifi()));
}
@Override
@@ -429,7 +439,7 @@
state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
}
} else if (cb.mNoDefaultNetwork) {
- if (cb.mNoNetworksAvailable) {
+ if (cb.mNoNetworksAvailable || !cb.mEnabled) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
@@ -489,7 +499,7 @@
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (cb.mNoDefaultNetwork) {
- if (cb.mNoNetworksAvailable) {
+ if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 0bbb5bd..4e936b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -16,12 +16,19 @@
package com.android.systemui.qs.tiles;
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.Manifest;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.view.View;
import android.widget.Switch;
@@ -38,18 +45,25 @@
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
+import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
/** Quick settings tile: Rotation **/
-public class RotationLockTile extends QSTileImpl<BooleanState> {
+public class RotationLockTile extends QSTileImpl<BooleanState> implements
+ BatteryController.BatteryStateChangeCallback {
private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
private final RotationLockController mController;
+ private final SensorPrivacyManager mPrivacyManager;
+ private final BatteryController mBatteryController;
+ private final SecureSetting mSetting;
@Inject
public RotationLockTile(
@@ -61,12 +75,38 @@
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- RotationLockController rotationLockController
+ RotationLockController rotationLockController,
+ SensorPrivacyManager privacyManager,
+ BatteryController batteryController,
+ SecureSettings secureSettings
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = rotationLockController;
mController.observe(this, mCallback);
+ mPrivacyManager = privacyManager;
+ mBatteryController = batteryController;
+ mPrivacyManager
+ .addSensorPrivacyListener(CAMERA, (sensor, enabled) -> refreshState());
+ int currentUser = host.getUserContext().getUserId();
+ mSetting = new SecureSetting(
+ secureSettings,
+ mHandler,
+ Secure.CAMERA_AUTOROTATE,
+ currentUser
+ ) {
+ @Override
+ protected void handleValueChanged(int value, boolean observedChange) {
+ // mHandler is the background handler so calling this is OK
+ handleRefreshState(value);
+ }
+ };
+ mBatteryController.observe(getLifecycle(), this);
+ }
+
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ refreshState();
}
@Override
@@ -95,14 +135,33 @@
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean rotationLocked = mController.isRotationLocked();
+ final boolean powerSave = mBatteryController.isPowerSave();
+ final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(
+ SensorPrivacyManager.Sensors.CAMERA);
+ final boolean cameraRotation =
+ !powerSave && !cameraLocked && hasSufficientPermission(mContext)
+ && mController.isCameraRotationEnabled();
state.value = !rotationLocked;
state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
state.icon = mIcon;
state.contentDescription = getAccessibilityString(rotationLocked);
+ if (!rotationLocked && cameraRotation) {
+ state.secondaryLabel = mContext.getResources().getString(
+ R.string.rotation_lock_camera_rotation_on);
+ } else {
+ state.secondaryLabel = "";
+ }
+
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ mSetting.setUserId(newUserId);
+ handleRefreshState(mSetting.getValue());
+ }
+
public static boolean isCurrentOrientationLockPortrait(RotationLockController controller,
Resources resources) {
int lockOrientation = controller.getRotationLockOrientation();
@@ -140,4 +199,11 @@
refreshState(rotationLocked);
}
};
+
+ private boolean hasSufficientPermission(Context context) {
+ final PackageManager packageManager = context.getPackageManager();
+ final String rotationPackage = packageManager.getRotationResolverPackageName();
+ return rotationPackage != null && packageManager.checkPermission(
+ Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
new file mode 100644
index 0000000..99eb5b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.Utils;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Adapter for showing Wi-Fi networks.
+ */
+public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> {
+
+ private static final String TAG = "InternetAdapter";
+ private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
+ private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+ private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
+
+ private final InternetDialogController mInternetDialogController;
+ private List<WifiEntry> mWifiEntries;
+ private int mWifiEntriesCount;
+
+ protected View mHolderView;
+ protected Context mContext;
+
+ public InternetAdapter(InternetDialogController controller) {
+ mInternetDialogController = controller;
+ }
+
+ @Override
+ public InternetViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+ int viewType) {
+ mContext = viewGroup.getContext();
+ mHolderView = LayoutInflater.from(mContext).inflate(R.layout.internet_list_item,
+ viewGroup, false);
+ return new InternetViewHolder(mHolderView, mInternetDialogController);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull InternetViewHolder viewHolder, int position) {
+ if (mWifiEntries == null || position >= mWifiEntriesCount) {
+ return;
+ }
+ viewHolder.onBind(mWifiEntries.get(position));
+ }
+
+ /**
+ * Updates the Wi-Fi networks.
+ *
+ * @param wifiEntries the updated Wi-Fi entries.
+ * @param wifiEntriesCount the total number of Wi-Fi entries.
+ */
+ public void setWifiEntries(@Nullable List<WifiEntry> wifiEntries, int wifiEntriesCount) {
+ mWifiEntries = wifiEntries;
+ mWifiEntriesCount = wifiEntriesCount;
+ }
+
+ /**
+ * Gets the total number of Wi-Fi networks.
+ *
+ * @return The total number of Wi-Fi entries.
+ */
+ @Override
+ public int getItemCount() {
+ return mWifiEntriesCount;
+ }
+
+ /**
+ * ViewHolder for binding Wi-Fi view.
+ */
+ static class InternetViewHolder extends RecyclerView.ViewHolder {
+
+ final LinearLayout mContainerLayout;
+ final LinearLayout mWifiListLayout;
+ final LinearLayout mWifiNetworkLayout;
+ final ImageView mWifiIcon;
+ final TextView mWifiTitleText;
+ final TextView mWifiSummaryText;
+ final ImageView mWifiEndIcon;
+ final Context mContext;
+ final InternetDialogController mInternetDialogController;
+
+ @VisibleForTesting
+ protected WifiUtils.InternetIconInjector mWifiIconInjector;
+
+ InternetViewHolder(View view, InternetDialogController internetDialogController) {
+ super(view);
+ mContext = view.getContext();
+ mInternetDialogController = internetDialogController;
+ mContainerLayout = view.requireViewById(R.id.internet_container);
+ mWifiListLayout = view.requireViewById(R.id.wifi_list);
+ mWifiNetworkLayout = view.requireViewById(R.id.wifi_network_layout);
+ mWifiIcon = view.requireViewById(R.id.wifi_icon);
+ mWifiTitleText = view.requireViewById(R.id.wifi_title);
+ mWifiSummaryText = view.requireViewById(R.id.wifi_summary);
+ mWifiEndIcon = view.requireViewById(R.id.wifi_end_icon);
+ mWifiIconInjector = mInternetDialogController.getWifiIconInjector();
+ }
+
+ void onBind(@NonNull WifiEntry wifiEntry) {
+ mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry));
+ setWifiNetworkLayout(wifiEntry.getTitle(),
+ Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY));
+
+ final int connectedState = wifiEntry.getConnectedState();
+ final int security = wifiEntry.getSecurity();
+ updateEndIcon(connectedState, security);
+
+ if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ mWifiListLayout.setOnClickListener(
+ v -> mInternetDialogController.launchWifiNetworkDetailsSetting(
+ wifiEntry.getKey()));
+ return;
+ }
+ mWifiListLayout.setOnClickListener(v -> {
+ if (wifiEntry.shouldEditBeforeConnect()) {
+ final Intent intent = new Intent(ACTION_WIFI_DIALOG);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey());
+ intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false);
+ mContext.startActivity(intent);
+ }
+ mInternetDialogController.connect(wifiEntry);
+ });
+ }
+
+ void setWifiNetworkLayout(CharSequence title, CharSequence summary) {
+ mWifiTitleText.setText(title);
+ if (TextUtils.isEmpty(summary)) {
+ mWifiSummaryText.setVisibility(View.GONE);
+ return;
+ }
+ mWifiSummaryText.setVisibility(View.VISIBLE);
+ mWifiSummaryText.setText(summary);
+ }
+
+ Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) {
+ if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+ return null;
+ }
+ final Drawable drawable = mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(),
+ wifiEntry.getLevel());
+ if (drawable == null) {
+ return null;
+ }
+ drawable.setTint(
+ Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorTertiary));
+ final AtomicReference<Drawable> shared = new AtomicReference<>();
+ shared.set(drawable);
+ return shared.get();
+ }
+
+ void updateEndIcon(int connectedState, int security) {
+ Drawable drawable = null;
+ if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ drawable = mContext.getDrawable(R.drawable.ic_settings_24dp);
+ } else if (security != WifiEntry.SECURITY_NONE && security != WifiEntry.SECURITY_OWE) {
+ drawable = mContext.getDrawable(R.drawable.ic_friction_lock_closed);
+ }
+ if (drawable == null) {
+ mWifiEndIcon.setVisibility(View.GONE);
+ return;
+ }
+ mWifiEndIcon.setVisibility(View.VISIBLE);
+ mWifiEndIcon.setImageDrawable(drawable);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
new file mode 100644
index 0000000..dae357e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -0,0 +1,623 @@
+/*
+ * 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.text.method.LinkMovementMethod;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.List;
+
+/**
+ * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks.
+ */
+@SysUISingleton
+public class InternetDialog extends SystemUIDialog implements
+ InternetDialogController.InternetDialogCallback, Window.Callback {
+ private static final String TAG = "InternetDialog";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final long PROGRESS_DELAY_MS = 2000L;
+
+ private final Handler mHandler;
+ private final LinearLayoutManager mLayoutManager;
+
+ @VisibleForTesting
+ protected InternetAdapter mAdapter;
+ @VisibleForTesting
+ protected WifiManager mWifiManager;
+ @VisibleForTesting
+ protected View mDialogView;
+ @VisibleForTesting
+ protected boolean mCanConfigWifi;
+
+ private InternetDialogFactory mInternetDialogFactory;
+ private SubscriptionManager mSubscriptionManager;
+ private TelephonyManager mTelephonyManager;
+ private AlertDialog mAlertDialog;
+ private UiEventLogger mUiEventLogger;
+ private Context mContext;
+ private InternetDialogController mInternetDialogController;
+ private TextView mInternetDialogTitle;
+ private TextView mInternetDialogSubTitle;
+ private View mDivider;
+ private ProgressBar mProgressBar;
+ private LinearLayout mInternetDialogLayout;
+ private LinearLayout mConnectedWifListLayout;
+ private LinearLayout mMobileNetworkLayout;
+ private LinearLayout mTurnWifiOnLayout;
+ private LinearLayout mEthernetLayout;
+ private TextView mWifiToggleTitleText;
+ private LinearLayout mWifiScanNotifyLayout;
+ private TextView mWifiScanNotifyText;
+ private LinearLayout mSeeAllLayout;
+ private RecyclerView mWifiRecyclerView;
+ private ImageView mConnectedWifiIcon;
+ private ImageView mWifiSettingsIcon;
+ private TextView mConnectedWifiTitleText;
+ private TextView mConnectedWifiSummaryText;
+ private ImageView mSignalIcon;
+ private TextView mMobileTitleText;
+ private TextView mMobileSummaryText;
+ private Switch mMobileDataToggle;
+ private Switch mWiFiToggle;
+ private FrameLayout mDoneLayout;
+ private Drawable mBackgroundOn;
+ private int mListMaxHeight;
+ private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private boolean mCanConfigMobileData;
+
+ // Wi-Fi entries
+ protected WifiEntry mConnectedWifiEntry;
+ protected int mWifiEntriesCount;
+
+ // Wi-Fi scanning progress bar
+ protected boolean mIsProgressBarVisible;
+ protected boolean mIsSearchingHidden;
+ protected final Runnable mHideProgressBarRunnable = () -> {
+ setProgressBarVisible(false);
+ };
+ protected Runnable mHideSearchingRunnable = () -> {
+ mIsSearchingHidden = true;
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ };
+
+ private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> {
+ // Set max height for list
+ if (mInternetDialogLayout.getHeight() > mListMaxHeight) {
+ ViewGroup.LayoutParams params = mInternetDialogLayout.getLayoutParams();
+ params.height = mListMaxHeight;
+ mInternetDialogLayout.setLayoutParams(params);
+ }
+ };
+
+ public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
+ InternetDialogController internetDialogController, boolean canConfigMobileData,
+ boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
+ @Main Handler handler) {
+ super(context, R.style.Theme_SystemUI_Dialog_Internet);
+ if (DEBUG) {
+ Log.d(TAG, "Init InternetDialog");
+ }
+ mContext = context;
+ mHandler = handler;
+ mInternetDialogFactory = internetDialogFactory;
+ mInternetDialogController = internetDialogController;
+ mSubscriptionManager = mInternetDialogController.getSubscriptionManager();
+ mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId();
+ mTelephonyManager = mInternetDialogController.getTelephonyManager();
+ mWifiManager = mInternetDialogController.getWifiManager();
+ mCanConfigMobileData = canConfigMobileData;
+ mCanConfigWifi = canConfigWifi;
+
+ mLayoutManager = new LinearLayoutManager(mContext) {
+ @Override
+ public boolean canScrollVertically() {
+ return false;
+ }
+ };
+ mListMaxHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.internet_dialog_list_max_height);
+ mUiEventLogger = uiEventLogger;
+ mAdapter = new InternetAdapter(mInternetDialogController);
+ if (!aboveStatusBar) {
+ getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) {
+ Log.d(TAG, "onCreate");
+ }
+ mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW);
+ mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog,
+ null);
+ final Window window = getWindow();
+ final WindowManager.LayoutParams layoutParams = window.getAttributes();
+ layoutParams.gravity = Gravity.BOTTOM;
+ // Move down the dialog to overlay the navigation bar.
+ layoutParams.setFitInsetsTypes(
+ layoutParams.getFitInsetsTypes() & ~WindowInsets.Type.navigationBars());
+ layoutParams.setFitInsetsSides(WindowInsets.Side.all());
+ layoutParams.setFitInsetsIgnoringVisibility(true);
+ window.setAttributes(layoutParams);
+ window.setContentView(mDialogView);
+ //Only fix the width for large screen or tablet.
+ window.setLayout(mContext.getResources().getDimensionPixelSize(
+ R.dimen.internet_dialog_list_max_width), ViewGroup.LayoutParams.WRAP_CONTENT);
+ window.setWindowAnimations(R.style.Animation_InternetDialog);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.addFlags(FLAG_LAYOUT_NO_LIMITS);
+
+ mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog);
+ mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
+ mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+ mDivider = mDialogView.requireViewById(R.id.divider);
+ mProgressBar = mDialogView.requireViewById(R.id.wifi_searching_progress);
+ mEthernetLayout = mDialogView.requireViewById(R.id.ethernet_layout);
+ mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+ mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title);
+ mWifiScanNotifyLayout = mDialogView.requireViewById(R.id.wifi_scan_notify_layout);
+ mWifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text);
+ mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout);
+ mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon);
+ mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title);
+ mConnectedWifiSummaryText = mDialogView.requireViewById(R.id.wifi_connected_summary);
+ mWifiSettingsIcon = mDialogView.requireViewById(R.id.wifi_settings_icon);
+ mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout);
+ mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout);
+ mDoneLayout = mDialogView.requireViewById(R.id.done_layout);
+ mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
+ mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
+ mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
+ mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
+ mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
+ mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
+ mInternetDialogLayout.getViewTreeObserver().addOnGlobalLayoutListener(
+ mInternetListLayoutListener);
+ mInternetDialogTitle.setText(getDialogTitleText());
+ mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+
+ setOnClickListener();
+ mTurnWifiOnLayout.setBackground(null);
+ mWifiRecyclerView.setLayoutManager(mLayoutManager);
+ mWifiRecyclerView.setAdapter(mAdapter);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (DEBUG) {
+ Log.d(TAG, "onStart");
+ }
+ mInternetDialogController.onStart(this, mCanConfigWifi);
+ if (!mCanConfigWifi) {
+ hideWifiViews();
+ }
+ }
+
+ @VisibleForTesting
+ void hideWifiViews() {
+ setProgressBarVisible(false);
+ mTurnWifiOnLayout.setVisibility(View.GONE);
+ mConnectedWifListLayout.setVisibility(View.GONE);
+ mWifiRecyclerView.setVisibility(View.GONE);
+ mSeeAllLayout.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (DEBUG) {
+ Log.d(TAG, "onStop");
+ }
+ mHandler.removeCallbacks(mHideProgressBarRunnable);
+ mHandler.removeCallbacks(mHideSearchingRunnable);
+ mMobileNetworkLayout.setOnClickListener(null);
+ mMobileDataToggle.setOnCheckedChangeListener(null);
+ mConnectedWifListLayout.setOnClickListener(null);
+ mSeeAllLayout.setOnClickListener(null);
+ mWiFiToggle.setOnCheckedChangeListener(null);
+ mDoneLayout.setOnClickListener(null);
+ mInternetDialogController.onStop();
+ mInternetDialogFactory.destroyDialog();
+ }
+
+ @Override
+ public void dismissDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "dismissDialog");
+ }
+ mInternetDialogFactory.destroyDialog();
+ dismiss();
+ }
+
+ void updateDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "updateDialog");
+ }
+ if (mInternetDialogController.isAirplaneModeEnabled()) {
+ mInternetDialogSubTitle.setVisibility(View.GONE);
+ } else {
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ }
+ updateEthernet();
+ setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()
+ || mInternetDialogController.isCarrierNetworkActive());
+
+ if (!mCanConfigWifi) {
+ return;
+ }
+
+ showProgressBar();
+ final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
+ final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+ final boolean isWifiScanEnabled = mWifiManager.isScanAlwaysAvailable();
+ updateWifiToggle(isWifiEnabled, isDeviceLocked);
+ updateConnectedWifi(isWifiEnabled, isDeviceLocked);
+ updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked);
+
+ final int visibility = (isDeviceLocked || !isWifiEnabled || mWifiEntriesCount <= 0)
+ ? View.GONE : View.VISIBLE;
+ mWifiRecyclerView.setVisibility(visibility);
+ mSeeAllLayout.setVisibility(visibility);
+ }
+
+ private void setOnClickListener() {
+ mMobileNetworkLayout.setOnClickListener(v -> {
+ if (mInternetDialogController.isMobileDataEnabled()
+ && !mInternetDialogController.isDeviceLocked()) {
+ if (!mInternetDialogController.activeNetworkIsCellular()) {
+ mInternetDialogController.connectCarrierNetwork();
+ }
+ }
+ });
+ mMobileDataToggle.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ if (!isChecked && shouldShowMobileDialog()) {
+ showTurnOffMobileDialog();
+ } else if (!shouldShowMobileDialog()) {
+ mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
+ isChecked, false);
+ }
+ });
+ mConnectedWifListLayout.setOnClickListener(v -> onClickConnectedWifi());
+ mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton());
+ mWiFiToggle.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ buttonView.setChecked(isChecked);
+ mWifiManager.setWifiEnabled(isChecked);
+ });
+ mDoneLayout.setOnClickListener(v -> dismiss());
+ }
+
+ @MainThread
+ private void updateEthernet() {
+ mEthernetLayout.setVisibility(
+ mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE);
+ }
+
+ private void setMobileDataLayout(boolean isCarrierNetworkConnected) {
+ if (mInternetDialogController.isAirplaneModeEnabled()
+ || !mInternetDialogController.hasCarrier()) {
+ mMobileNetworkLayout.setVisibility(View.GONE);
+ } else {
+ mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
+ mMobileNetworkLayout.setVisibility(View.VISIBLE);
+ mMobileTitleText.setText(getMobileNetworkTitle());
+ if (!TextUtils.isEmpty(getMobileNetworkSummary())) {
+ mMobileSummaryText.setText(
+ Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY));
+ mMobileSummaryText.setVisibility(View.VISIBLE);
+ } else {
+ mMobileSummaryText.setVisibility(View.GONE);
+ }
+ mSignalIcon.setImageDrawable(getSignalStrengthDrawable());
+ mMobileTitleText.setTextAppearance(isCarrierNetworkConnected
+ ? R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+ mMobileSummaryText.setTextAppearance(isCarrierNetworkConnected
+ ? R.style.TextAppearance_InternetDialog_Secondary_Active
+ : R.style.TextAppearance_InternetDialog_Secondary);
+ mMobileNetworkLayout.setBackground(isCarrierNetworkConnected ? mBackgroundOn : null);
+
+ mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ @MainThread
+ private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
+ mWiFiToggle.setChecked(isWifiEnabled);
+ if (isDeviceLocked) {
+ mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null)
+ ? R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+ }
+ mTurnWifiOnLayout.setBackground(
+ (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
+ }
+
+ @MainThread
+ private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
+ if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
+ mConnectedWifListLayout.setVisibility(View.GONE);
+ return;
+ }
+ mConnectedWifListLayout.setVisibility(View.VISIBLE);
+ mConnectedWifiTitleText.setText(mConnectedWifiEntry.getTitle());
+ mConnectedWifiSummaryText.setText(mConnectedWifiEntry.getSummary(false));
+ mConnectedWifiIcon.setImageDrawable(
+ mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry));
+ mWifiSettingsIcon.setColorFilter(
+ mContext.getColor(R.color.connected_network_primary_color));
+ }
+
+ @MainThread
+ private void updateWifiScanNotify(boolean isWifiEnabled, boolean isWifiScanEnabled,
+ boolean isDeviceLocked) {
+ if (isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) {
+ mWifiScanNotifyLayout.setVisibility(View.GONE);
+ return;
+ }
+ if (TextUtils.isEmpty(mWifiScanNotifyText.getText())) {
+ final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo(
+ AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION,
+ v -> mInternetDialogController.launchWifiScanningSetting());
+ mWifiScanNotifyText.setText(AnnotationLinkSpan.linkify(
+ getContext().getText(R.string.wifi_scan_notify_message), linkInfo));
+ mWifiScanNotifyText.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ mWifiScanNotifyLayout.setVisibility(View.VISIBLE);
+ }
+
+ void onClickConnectedWifi() {
+ if (mConnectedWifiEntry == null) {
+ return;
+ }
+ mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey());
+ }
+
+ void onClickSeeMoreButton() {
+ mInternetDialogController.launchNetworkSetting();
+ }
+
+ CharSequence getDialogTitleText() {
+ return mInternetDialogController.getDialogTitleText();
+ }
+
+ CharSequence getSubtitleText() {
+ return mInternetDialogController.getSubtitleText(
+ mIsProgressBarVisible && !mIsSearchingHidden);
+ }
+
+ private Drawable getSignalStrengthDrawable() {
+ return mInternetDialogController.getSignalStrengthDrawable();
+ }
+
+ CharSequence getMobileNetworkTitle() {
+ return mInternetDialogController.getMobileNetworkTitle();
+ }
+
+ String getMobileNetworkSummary() {
+ return mInternetDialogController.getMobileNetworkSummary();
+ }
+
+ protected void showProgressBar() {
+ if (mWifiManager == null || !mWifiManager.isWifiEnabled()
+ || mInternetDialogController.isDeviceLocked()) {
+ setProgressBarVisible(false);
+ return;
+ }
+ setProgressBarVisible(true);
+ if (mConnectedWifiEntry != null || mWifiEntriesCount > 0) {
+ mHandler.postDelayed(mHideProgressBarRunnable, PROGRESS_DELAY_MS);
+ } else if (!mIsSearchingHidden) {
+ mHandler.postDelayed(mHideSearchingRunnable, PROGRESS_DELAY_MS);
+ }
+ }
+
+ private void setProgressBarVisible(boolean visible) {
+ if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null
+ && mAdapter.mHolderView.isAttachedToWindow()) {
+ mIsProgressBarVisible = true;
+ }
+ mIsProgressBarVisible = visible;
+ mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
+ mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ }
+
+ private boolean shouldShowMobileDialog() {
+ boolean flag = Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA,
+ false);
+ if (mInternetDialogController.isMobileDataEnabled() && !flag) {
+ return true;
+ }
+ return false;
+ }
+
+ private void showTurnOffMobileDialog() {
+ CharSequence carrierName = getMobileNetworkTitle();
+ boolean isInService = mInternetDialogController.isVoiceStateInService();
+ if (TextUtils.isEmpty(carrierName) || !isInService) {
+ carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
+ }
+ mAlertDialog = new Builder(mContext)
+ .setTitle(R.string.mobile_data_disable_title)
+ .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
+ .setNegativeButton(android.R.string.cancel, (d, w) -> {
+ mMobileDataToggle.setChecked(true);
+ })
+ .setPositiveButton(
+ com.android.internal.R.string.alert_windows_notification_turn_off_action,
+ (d, w) -> {
+ mInternetDialogController.setMobileDataEnabled(mContext,
+ mDefaultDataSubId, false, false);
+ mMobileDataToggle.setChecked(false);
+ Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
+ })
+ .create();
+ mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true));
+ mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
+ SystemUIDialog.registerDismissListener(mAlertDialog);
+ SystemUIDialog.setWindowOnTop(mAlertDialog);
+ mAlertDialog.show();
+ }
+
+ @Override
+ public void onRefreshCarrierInfo() {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSimStateChanged() {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onLost(Network network) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSubscriptionsChanged(int defaultDataSubId) {
+ mDefaultDataSubId = defaultDataSubId;
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
+ @Nullable WifiEntry connectedEntry) {
+ mConnectedWifiEntry = connectedEntry;
+ mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+ mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
+ mHandler.post(() -> {
+ mAdapter.notifyDataSetChanged();
+ updateDialog();
+ });
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (mAlertDialog != null && !mAlertDialog.isShowing()) {
+ if (!hasFocus && isShowing()) {
+ dismiss();
+ }
+ }
+ }
+
+ public enum InternetDialogEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The Internet dialog became visible on the screen.")
+ INTERNET_DIALOG_SHOW(843);
+
+ private final int mId;
+
+ InternetDialogEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
new file mode 100644
index 0000000..276c0be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.android.settingslib.mobile.MobileMappings.getIconKey;
+import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.DeviceInfoUtils;
+import com.android.settingslib.SignalIcon;
+import com.android.settingslib.Utils;
+import com.android.settingslib.graph.SignalDrawable;
+import com.android.settingslib.mobile.MobileMappings;
+import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.settingslib.net.SignalStrengthUtil;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
+import com.android.systemui.toast.SystemUIToast;
+import com.android.systemui.toast.ToastFactory;
+import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.wifitrackerlib.MergedCarrierEntry;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+public class InternetDialogController implements WifiEntry.DisconnectCallback,
+ NetworkController.AccessPointController.AccessPointCallback {
+
+ private static final String TAG = "InternetDialogController";
+ private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+ "android.settings.NETWORK_PROVIDER_SETTINGS";
+ private static final String ACTION_WIFI_SCANNING_SETTINGS =
+ "android.settings.WIFI_SCANNING_SETTINGS";
+ private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+ public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
+ public static final int NO_CELL_DATA_TYPE_ICON = 0;
+ private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
+ private static final int SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT =
+ R.string.tap_a_network_to_connect;
+ private static final int SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS =
+ R.string.unlock_to_view_networks;
+ private static final int SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS =
+ R.string.wifi_empty_list_wifi_on;
+ private static final int SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE =
+ R.string.non_carrier_network_unavailable;
+ private static final int SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE =
+ R.string.all_network_unavailable;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final int MAX_WIFI_ENTRY_COUNT = 4;
+
+ private WifiManager mWifiManager;
+ private Context mContext;
+ private SubscriptionManager mSubscriptionManager;
+ private TelephonyManager mTelephonyManager;
+ private ConnectivityManager mConnectivityManager;
+ private CarrierConfigTracker mCarrierConfigTracker;
+ private TelephonyDisplayInfo mTelephonyDisplayInfo =
+ new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+ private Handler mHandler;
+ private Handler mWorkerHandler;
+ private MobileMappings.Config mConfig = null;
+ private Executor mExecutor;
+ private AccessPointController mAccessPointController;
+ private IntentFilter mConnectionStateFilter;
+ private InternetDialogCallback mCallback;
+ private WifiEntry mConnectedEntry;
+ private int mWifiEntriesCount;
+ private UiEventLogger mUiEventLogger;
+ private BroadcastDispatcher mBroadcastDispatcher;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private GlobalSettings mGlobalSettings;
+ private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
+ private WindowManager mWindowManager;
+ private ToastFactory mToastFactory;
+
+ @VisibleForTesting
+ static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
+ @VisibleForTesting
+ static final float TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f;
+ @VisibleForTesting
+ static final long SHORT_DURATION_TIMEOUT = 4000;
+ @VisibleForTesting
+ protected ActivityStarter mActivityStarter;
+ @VisibleForTesting
+ protected SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
+ @VisibleForTesting
+ protected InternetTelephonyCallback mInternetTelephonyCallback;
+ @VisibleForTesting
+ protected WifiUtils.InternetIconInjector mWifiIconInjector;
+ @VisibleForTesting
+ protected boolean mCanConfigWifi;
+ @VisibleForTesting
+ protected KeyguardStateController mKeyguardStateController;
+ @VisibleForTesting
+ protected boolean mHasEthernet = false;
+
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ mCallback.onRefreshCarrierInfo();
+ }
+
+ @Override
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ mCallback.onSimStateChanged();
+ }
+ };
+
+ protected List<SubscriptionInfo> getSubscriptionInfo() {
+ return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+ }
+
+ @Inject
+ public InternetDialogController(@NonNull Context context, UiEventLogger uiEventLogger,
+ ActivityStarter starter, AccessPointController accessPointController,
+ SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
+ @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+ @Main Handler handler, @Main Executor mainExecutor,
+ BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
+ WindowManager windowManager, ToastFactory toastFactory,
+ @Background Handler workerHandler,
+ CarrierConfigTracker carrierConfigTracker) {
+ if (DEBUG) {
+ Log.d(TAG, "Init InternetDialogController");
+ }
+ mHandler = handler;
+ mWorkerHandler = workerHandler;
+ mExecutor = mainExecutor;
+ mContext = context;
+ mGlobalSettings = globalSettings;
+ mWifiManager = wifiManager;
+ mTelephonyManager = telephonyManager;
+ mConnectivityManager = connectivityManager;
+ mSubscriptionManager = subscriptionManager;
+ mCarrierConfigTracker = carrierConfigTracker;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
+ mConnectionStateFilter = new IntentFilter();
+ mConnectionStateFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+ mConnectionStateFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+ mUiEventLogger = uiEventLogger;
+ mActivityStarter = starter;
+ mAccessPointController = accessPointController;
+ mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
+ mConnectivityManagerNetworkCallback = new DataConnectivityListener();
+ mWindowManager = windowManager;
+ mToastFactory = toastFactory;
+ }
+
+ void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
+ if (DEBUG) {
+ Log.d(TAG, "onStart");
+ }
+ mCallback = callback;
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+ mAccessPointController.addAccessPointCallback(this);
+ mBroadcastDispatcher.registerReceiver(mConnectionStateReceiver, mConnectionStateFilter,
+ mExecutor);
+ // Listen the subscription changes
+ mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
+ mOnSubscriptionsChangedListener);
+ mDefaultDataSubId = getDefaultDataSubscriptionId();
+ if (DEBUG) {
+ Log.d(TAG, "Init, SubId: " + mDefaultDataSubId);
+ }
+ mConfig = MobileMappings.Config.readConfig(mContext);
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mInternetTelephonyCallback = new InternetTelephonyCallback();
+ mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+ // Listen the connectivity changes
+ mConnectivityManager.registerDefaultNetworkCallback(mConnectivityManagerNetworkCallback);
+ mCanConfigWifi = canConfigWifi;
+ scanWifiAccessPoints();
+ }
+
+ void onStop() {
+ if (DEBUG) {
+ Log.d(TAG, "onStop");
+ }
+ mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver);
+ mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ mSubscriptionManager.removeOnSubscriptionsChangedListener(
+ mOnSubscriptionsChangedListener);
+ mAccessPointController.removeAccessPointCallback(this);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+ mConnectivityManager.unregisterNetworkCallback(mConnectivityManagerNetworkCallback);
+ }
+
+ @VisibleForTesting
+ boolean isAirplaneModeEnabled() {
+ return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+ }
+
+ @VisibleForTesting
+ protected int getDefaultDataSubscriptionId() {
+ return mSubscriptionManager.getDefaultDataSubscriptionId();
+ }
+
+ @VisibleForTesting
+ protected Intent getSettingsIntent() {
+ return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ protected Intent getWifiDetailsSettingsIntent(String key) {
+ if (TextUtils.isEmpty(key)) {
+ if (DEBUG) {
+ Log.d(TAG, "connected entry's key is empty");
+ }
+ return null;
+ }
+ return WifiUtils.getWifiDetailsSettingsIntent(key);
+ }
+
+ CharSequence getDialogTitleText() {
+ if (isAirplaneModeEnabled()) {
+ return mContext.getText(R.string.airplane_mode);
+ }
+ return mContext.getText(R.string.quick_settings_internet_label);
+ }
+
+ CharSequence getSubtitleText(boolean isProgressBarVisible) {
+ if (isAirplaneModeEnabled()) {
+ return null;
+ }
+
+ if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) {
+ // When the airplane mode is off and Wi-Fi is disabled.
+ // Sub-Title: Wi-Fi is off
+ if (DEBUG) {
+ Log.d(TAG, "Airplane mode off + Wi-Fi off.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF);
+ }
+
+ if (isDeviceLocked()) {
+ // When the device is locked.
+ // Sub-Title: Unlock to view networks
+ if (DEBUG) {
+ Log.d(TAG, "The device is locked.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS);
+ }
+
+ if (mConnectedEntry != null || mWifiEntriesCount > 0) {
+ return mCanConfigWifi ? mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT) : null;
+ }
+
+ if (mCanConfigWifi && isProgressBarVisible) {
+ // When the Wi-Fi scan result callback is received
+ // Sub-Title: Searching for networks...
+ return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS);
+ }
+
+ // Sub-Title:
+ // show non_carrier_network_unavailable
+ // - while Wi-Fi on + no Wi-Fi item
+ // - while Wi-Fi on + no Wi-Fi item + mobile data off
+ // show all_network_unavailable:
+ // - while Wi-Fi on + no Wi-Fi item + no carrier item
+ // - while Wi-Fi on + no Wi-Fi item + service is out of service
+ // - while Wi-Fi on + no Wi-Fi item + mobile data on + no carrier data.
+ if (DEBUG) {
+ Log.d(TAG, "No Wi-Fi item.");
+ }
+ if (!hasCarrier() || (!isVoiceStateInService() && !isDataStateInService())) {
+ if (DEBUG) {
+ Log.d(TAG, "No carrier or service is out of service.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (mCanConfigWifi && !isMobileDataEnabled()) {
+ if (DEBUG) {
+ Log.d(TAG, "Mobile data off");
+ }
+ return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (!activeNetworkIsCellular()) {
+ if (DEBUG) {
+ Log.d(TAG, "No carrier data.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (mCanConfigWifi) {
+ return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+ }
+ return null;
+ }
+
+ Drawable getInternetWifiDrawable(@NonNull WifiEntry wifiEntry) {
+ if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+ return null;
+ }
+ final Drawable drawable =
+ mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(), wifiEntry.getLevel());
+ if (drawable == null) {
+ return null;
+ }
+ drawable.setTint(mContext.getColor(R.color.connected_network_primary_color));
+ return drawable;
+ }
+
+ Drawable getSignalStrengthDrawable() {
+ Drawable drawable = mContext.getDrawable(
+ R.drawable.ic_signal_strength_zero_bar_no_internet);
+ try {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null");
+ }
+ return drawable;
+ }
+
+ if (isDataStateInService() || isVoiceStateInService()) {
+ AtomicReference<Drawable> shared = new AtomicReference<>();
+ shared.set(getSignalStrengthDrawableWithLevel());
+ drawable = shared.get();
+ }
+
+ int tintColor = Utils.getColorAttrDefaultColor(mContext,
+ android.R.attr.textColorTertiary);
+ if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
+ tintColor = mContext.getColor(R.color.connected_network_primary_color);
+ }
+ drawable.setTint(tintColor);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return drawable;
+ }
+
+ /**
+ * To get the signal bar icon with level.
+ *
+ * @return The Drawable which is a signal bar icon with level.
+ */
+ Drawable getSignalStrengthDrawableWithLevel() {
+ final SignalStrength strength = mTelephonyManager.getSignalStrength();
+ int level = (strength == null) ? 0 : strength.getLevel();
+ int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+ if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) {
+ level += 1;
+ numLevels += 1;
+ }
+ return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
+ !isMobileDataEnabled());
+ }
+
+ Drawable getSignalStrengthIcon(Context context, int level, int numLevels,
+ int iconType, boolean cutOut) {
+ Log.d(TAG, "getSignalStrengthIcon");
+ final SignalDrawable signalDrawable = new SignalDrawable(context);
+ signalDrawable.setLevel(
+ SignalDrawable.getState(level, numLevels, cutOut));
+
+ // Make the network type drawable
+ final Drawable networkDrawable =
+ iconType == NO_CELL_DATA_TYPE_ICON
+ ? EMPTY_DRAWABLE
+ : context.getResources().getDrawable(iconType, context.getTheme());
+
+ // Overlay the two drawables
+ final Drawable[] layers = {networkDrawable, signalDrawable};
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size);
+
+ final LayerDrawable icons = new LayerDrawable(layers);
+ // Set the network type icon at the top left
+ icons.setLayerGravity(0 /* index of networkDrawable */, Gravity.TOP | Gravity.LEFT);
+ // Set the signal strength icon at the bottom right
+ icons.setLayerGravity(1 /* index of SignalDrawable */, Gravity.BOTTOM | Gravity.RIGHT);
+ icons.setLayerSize(1 /* index of SignalDrawable */, iconSize, iconSize);
+ icons.setTintList(Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+ return icons;
+ }
+
+ private boolean shouldInflateSignalStrength(int subId) {
+ return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId);
+ }
+
+ private CharSequence getUniqueSubscriptionDisplayName(int subscriptionId, Context context) {
+ final Map<Integer, CharSequence> displayNames = getUniqueSubscriptionDisplayNames(context);
+ return displayNames.getOrDefault(subscriptionId, "");
+ }
+
+ private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) {
+ class DisplayInfo {
+ public SubscriptionInfo subscriptionInfo;
+ public CharSequence originalName;
+ public CharSequence uniqueName;
+ }
+
+ // Map of SubscriptionId to DisplayName
+ final Supplier<Stream<DisplayInfo>> originalInfos =
+ () -> getSubscriptionInfo()
+ .stream()
+ .filter(i -> {
+ // Filter out null values.
+ return (i != null && i.getDisplayName() != null);
+ })
+ .map(i -> {
+ DisplayInfo info = new DisplayInfo();
+ info.subscriptionInfo = i;
+ info.originalName = i.getDisplayName().toString().trim();
+ return info;
+ });
+
+ // A Unique set of display names
+ Set<CharSequence> uniqueNames = new HashSet<>();
+ // Return the set of duplicate names
+ final Set<CharSequence> duplicateOriginalNames = originalInfos.get()
+ .filter(info -> !uniqueNames.add(info.originalName))
+ .map(info -> info.originalName)
+ .collect(Collectors.toSet());
+
+ // If a display name is duplicate, append the final 4 digits of the phone number.
+ // Creates a mapping of Subscription id to original display name + phone number display name
+ final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> {
+ if (duplicateOriginalNames.contains(info.originalName)) {
+ // This may return null, if the user cannot view the phone number itself.
+ final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context,
+ info.subscriptionInfo);
+ String lastFourDigits = "";
+ if (phoneNumber != null) {
+ lastFourDigits = (phoneNumber.length() > 4)
+ ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber;
+ }
+
+ if (TextUtils.isEmpty(lastFourDigits)) {
+ info.uniqueName = info.originalName;
+ } else {
+ info.uniqueName = info.originalName + " " + lastFourDigits;
+ }
+
+ } else {
+ info.uniqueName = info.originalName;
+ }
+ return info;
+ });
+
+ // Check uniqueness a second time.
+ // We might not have had permission to view the phone numbers.
+ // There might also be multiple phone numbers whose last 4 digits the same.
+ uniqueNames.clear();
+ final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get()
+ .filter(info -> !uniqueNames.add(info.uniqueName))
+ .map(info -> info.uniqueName)
+ .collect(Collectors.toSet());
+
+ return uniqueInfos.get().map(info -> {
+ if (duplicatePhoneNames.contains(info.uniqueName)) {
+ info.uniqueName = info.originalName + " "
+ + info.subscriptionInfo.getSubscriptionId();
+ }
+ return info;
+ }).collect(Collectors.toMap(
+ info -> info.subscriptionInfo.getSubscriptionId(),
+ info -> info.uniqueName));
+ }
+
+ CharSequence getMobileNetworkTitle() {
+ return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext);
+ }
+
+ String getMobileNetworkSummary() {
+ String description = getNetworkTypeDescription(mContext, mConfig,
+ mTelephonyDisplayInfo, mDefaultDataSubId);
+ return getMobileSummary(mContext, description);
+ }
+
+ /**
+ * Get currently description of mobile network type.
+ */
+ private String getNetworkTypeDescription(Context context, MobileMappings.Config config,
+ TelephonyDisplayInfo telephonyDisplayInfo, int subId) {
+ String iconKey = getIconKey(telephonyDisplayInfo);
+
+ if (mapIconSets(config) == null || mapIconSets(config).get(iconKey) == null) {
+ if (DEBUG) {
+ Log.d(TAG, "The description of network type is empty.");
+ }
+ return "";
+ }
+
+ int resId = mapIconSets(config).get(iconKey).dataContentDescription;
+ if (isCarrierNetworkActive()) {
+ SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
+ TelephonyIcons.CARRIER_MERGED_WIFI;
+ resId = carrierMergedWifiIconGroup.dataContentDescription;
+ }
+
+ return resId != 0
+ ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : "";
+ }
+
+ private String getMobileSummary(Context context, String networkTypeDescription) {
+ if (!isMobileDataEnabled()) {
+ return context.getString(R.string.mobile_data_off_summary);
+ }
+ if (!isDataStateInService()) {
+ return context.getString(R.string.mobile_data_no_connection);
+ }
+ String summary = networkTypeDescription;
+ if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
+ summary = context.getString(R.string.preference_summary_default_combination,
+ context.getString(R.string.mobile_data_connection_active),
+ networkTypeDescription);
+ }
+ return summary;
+ }
+
+ void launchNetworkSetting() {
+ mCallback.dismissDialog();
+ mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
+ }
+
+ void launchWifiNetworkDetailsSetting(String key) {
+ Intent intent = getWifiDetailsSettingsIntent(key);
+ if (intent != null) {
+ mCallback.dismissDialog();
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+ }
+
+ void launchWifiScanningSetting() {
+ mCallback.dismissDialog();
+ final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+
+ void connectCarrierNetwork() {
+ final MergedCarrierEntry mergedCarrierEntry =
+ mAccessPointController.getMergedCarrierEntry();
+ if (mergedCarrierEntry != null && mergedCarrierEntry.canConnect()) {
+ mergedCarrierEntry.connect(null /* ConnectCallback */, false);
+ makeOverlayToast(R.string.wifi_wont_autoconnect_for_now);
+ }
+ }
+
+ boolean isCarrierNetworkActive() {
+ final MergedCarrierEntry mergedCarrierEntry =
+ mAccessPointController.getMergedCarrierEntry();
+ return mergedCarrierEntry != null && mergedCarrierEntry.isDefaultNetwork();
+ }
+
+ @WorkerThread
+ void setMergedCarrierWifiEnabledIfNeed(int subId, boolean enabled) {
+ // If the Carrier Provisions Wi-Fi Merged Networks enabled, do not set the merged carrier
+ // Wi-Fi state together.
+ if (mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(subId)) {
+ return;
+ }
+
+ final MergedCarrierEntry entry = mAccessPointController.getMergedCarrierEntry();
+ if (entry == null) {
+ if (DEBUG) {
+ Log.d(TAG, "MergedCarrierEntry is null, can not set the status.");
+ }
+ return;
+ }
+ entry.setEnabled(enabled);
+ }
+
+ WifiManager getWifiManager() {
+ return mWifiManager;
+ }
+
+ TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ SubscriptionManager getSubscriptionManager() {
+ return mSubscriptionManager;
+ }
+
+ /**
+ * @return whether there is the carrier item in the slice.
+ */
+ boolean hasCarrier() {
+ if (mSubscriptionManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
+ }
+ return false;
+ }
+
+ if (isAirplaneModeEnabled() || mTelephonyManager == null
+ || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return {@code true} if mobile data is enabled
+ */
+ boolean isMobileDataEnabled() {
+ if (mTelephonyManager == null || !mTelephonyManager.isDataEnabled()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set whether to enable data for {@code subId}, also whether to disable data for other
+ * subscription
+ */
+ void setMobileDataEnabled(Context context, int subId, boolean enabled,
+ boolean disableOtherSubscriptions) {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null, can not set mobile data.");
+ }
+ return;
+ }
+
+ if (mSubscriptionManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "SubscriptionManager is null, can not set mobile data.");
+ }
+ return;
+ }
+
+ mTelephonyManager.setDataEnabled(enabled);
+ if (disableOtherSubscriptions) {
+ final List<SubscriptionInfo> subInfoList =
+ mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (subInfoList != null) {
+ for (SubscriptionInfo subInfo : subInfoList) {
+ // We never disable mobile data for opportunistic subscriptions.
+ if (subInfo.getSubscriptionId() != subId && !subInfo.isOpportunistic()) {
+ context.getSystemService(TelephonyManager.class).createForSubscriptionId(
+ subInfo.getSubscriptionId()).setDataEnabled(false);
+ }
+ }
+ }
+ }
+ mWorkerHandler.post(() -> setMergedCarrierWifiEnabledIfNeed(subId, enabled));
+ }
+
+ boolean isDataStateInService() {
+ final ServiceState serviceState = mTelephonyManager.getServiceState();
+ NetworkRegistrationInfo regInfo =
+ (serviceState == null) ? null : serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ return (regInfo == null) ? false : regInfo.isRegistered();
+ }
+
+ boolean isVoiceStateInService() {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null, can not detect voice state.");
+ }
+ return false;
+ }
+
+ final ServiceState serviceState = mTelephonyManager.getServiceState();
+ return serviceState != null
+ && serviceState.getState() == serviceState.STATE_IN_SERVICE;
+ }
+
+ public boolean isDeviceLocked() {
+ return !mKeyguardStateController.isUnlocked();
+ }
+
+ boolean activeNetworkIsCellular() {
+ if (mConnectivityManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "ConnectivityManager is null, can not check active network.");
+ }
+ return false;
+ }
+
+ final Network activeNetwork = mConnectivityManager.getActiveNetwork();
+ if (activeNetwork == null) {
+ return false;
+ }
+ final NetworkCapabilities networkCapabilities =
+ mConnectivityManager.getNetworkCapabilities(activeNetwork);
+ if (networkCapabilities == null) {
+ return false;
+ }
+ return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
+ }
+
+ boolean connect(WifiEntry ap) {
+ if (ap == null) {
+ if (DEBUG) {
+ Log.d(TAG, "No Wi-Fi ap to connect.");
+ }
+ return false;
+ }
+
+ if (ap.getWifiConfiguration() != null) {
+ if (DEBUG) {
+ Log.d(TAG, "connect networkId=" + ap.getWifiConfiguration().networkId);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "connect to unsaved network " + ap.getTitle());
+ }
+ }
+ ap.connect(new WifiEntryConnectCallback(mActivityStarter, ap, this));
+ return false;
+ }
+
+ static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback {
+ final ActivityStarter mActivityStarter;
+ final WifiEntry mWifiEntry;
+ final InternetDialogController mInternetDialogController;
+
+ WifiEntryConnectCallback(ActivityStarter activityStarter, WifiEntry connectWifiEntry,
+ InternetDialogController internetDialogController) {
+ mActivityStarter = activityStarter;
+ mWifiEntry = connectWifiEntry;
+ mInternetDialogController = internetDialogController;
+ }
+
+ @Override
+ public void onConnectResult(@ConnectStatus int status) {
+ if (DEBUG) {
+ Log.d(TAG, "onConnectResult " + status);
+ }
+
+ if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
+ final Intent intent = new Intent("com.android.settings.WIFI_DIALOG")
+ .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
+ } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
+ mInternetDialogController.makeOverlayToast(R.string.wifi_failed_connect_message);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "connect failure reason=" + status);
+ }
+ }
+ }
+ }
+
+ private void scanWifiAccessPoints() {
+ if (mCanConfigWifi) {
+ mAccessPointController.scanForAccessPoints();
+ }
+ }
+
+ @Override
+ @WorkerThread
+ public void onAccessPointsChanged(List<WifiEntry> accessPoints) {
+ if (!mCanConfigWifi) {
+ return;
+ }
+
+ if (accessPoints == null || accessPoints.size() == 0) {
+ mConnectedEntry = null;
+ mWifiEntriesCount = 0;
+ if (mCallback != null) {
+ mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ }
+ return;
+ }
+
+ boolean hasConnectedWifi = false;
+ final int accessPointSize = accessPoints.size();
+ for (int i = 0; i < accessPointSize; i++) {
+ WifiEntry wifiEntry = accessPoints.get(i);
+ if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) {
+ mConnectedEntry = wifiEntry;
+ hasConnectedWifi = true;
+ break;
+ }
+ }
+ if (!hasConnectedWifi) {
+ mConnectedEntry = null;
+ }
+
+ int count = MAX_WIFI_ENTRY_COUNT;
+ if (mHasEthernet) {
+ count -= 1;
+ }
+ if (hasCarrier()) {
+ count -= 1;
+ }
+ if (hasConnectedWifi) {
+ count -= 1;
+ }
+ final List<WifiEntry> wifiEntries = accessPoints.stream()
+ .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork()
+ || !wifiEntry.hasInternetAccess()))
+ .limit(count)
+ .collect(Collectors.toList());
+ mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+
+ if (mCallback != null) {
+ mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
+ }
+ }
+
+ @Override
+ public void onSettingsActivityTriggered(Intent settingsIntent) {
+ }
+
+ @Override
+ public void onDisconnectResult(int status) {
+ }
+
+ private class InternetTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DisplayInfoListener,
+ TelephonyCallback.ServiceStateListener,
+ TelephonyCallback.SignalStrengthsListener {
+
+ @Override
+ public void onServiceStateChanged(@NonNull ServiceState serviceState) {
+ mCallback.onServiceStateChanged(serviceState);
+ }
+
+ @Override
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ mCallback.onDataConnectionStateChanged(state, networkType);
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
+ mCallback.onSignalStrengthsChanged(signalStrength);
+ }
+
+ @Override
+ public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
+ mTelephonyDisplayInfo = telephonyDisplayInfo;
+ mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
+ }
+ }
+
+ private class InternetOnSubscriptionChangedListener
+ extends SubscriptionManager.OnSubscriptionsChangedListener {
+ InternetOnSubscriptionChangedListener() {
+ super();
+ }
+
+ @Override
+ public void onSubscriptionsChanged() {
+ updateListener();
+ }
+ }
+
+ private class DataConnectivityListener extends ConnectivityManager.NetworkCallback {
+ @Override
+ @WorkerThread
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities capabilities) {
+ mHasEthernet = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET);
+ if (mCanConfigWifi && (mHasEthernet || capabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_WIFI))) {
+ scanWifiAccessPoints();
+ }
+ // update UI
+ mCallback.onCapabilitiesChanged(network, capabilities);
+ }
+
+ @Override
+ @WorkerThread
+ public void onLost(@NonNull Network network) {
+ mHasEthernet = false;
+ mCallback.onLost(network);
+ }
+ }
+
+ /**
+ * Return {@code true} If the Ethernet exists
+ */
+ @MainThread
+ public boolean hasEthernet() {
+ return mHasEthernet;
+ }
+
+ private final BroadcastReceiver mConnectionStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
+ if (DEBUG) {
+ Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
+ }
+ mConfig = MobileMappings.Config.readConfig(context);
+ updateListener();
+ } else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
+ updateListener();
+ }
+ }
+ };
+
+ private void updateListener() {
+ int defaultDataSubId = getDefaultDataSubscriptionId();
+ if (mDefaultDataSubId == getDefaultDataSubscriptionId()) {
+ if (DEBUG) {
+ Log.d(TAG, "DDS: no change");
+ }
+ return;
+ }
+
+ mDefaultDataSubId = defaultDataSubId;
+ if (DEBUG) {
+ Log.d(TAG, "DDS: defaultDataSubId:" + mDefaultDataSubId);
+ }
+ if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) {
+ mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mTelephonyManager.registerTelephonyCallback(mHandler::post,
+ mInternetTelephonyCallback);
+ mCallback.onSubscriptionsChanged(mDefaultDataSubId);
+ }
+ }
+
+ public WifiUtils.InternetIconInjector getWifiIconInjector() {
+ return mWifiIconInjector;
+ }
+
+ interface InternetDialogCallback {
+
+ void onRefreshCarrierInfo();
+
+ void onSimStateChanged();
+
+ void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities);
+
+ void onLost(@NonNull Network network);
+
+ void onSubscriptionsChanged(int defaultDataSubId);
+
+ void onServiceStateChanged(ServiceState serviceState);
+
+ void onDataConnectionStateChanged(int state, int networkType);
+
+ void onSignalStrengthsChanged(SignalStrength signalStrength);
+
+ void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo);
+
+ void dismissDialog();
+
+ void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
+ @Nullable WifiEntry connectedEntry);
+ }
+
+ void makeOverlayToast(int stringId) {
+ final Resources res = mContext.getResources();
+
+ final SystemUIToast systemUIToast = mToastFactory.createToast(mContext,
+ res.getString(stringId), mContext.getPackageName(), UserHandle.myUserId(),
+ res.getConfiguration().orientation);
+ if (systemUIToast == null) {
+ return;
+ }
+
+ View toastView = systemUIToast.getView();
+
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.format = PixelFormat.TRANSLUCENT;
+ params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+ params.y = systemUIToast.getYOffset();
+
+ int absGravity = Gravity.getAbsoluteGravity(systemUIToast.getGravity(),
+ res.getConfiguration().getLayoutDirection());
+ params.gravity = absGravity;
+ if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+ params.horizontalWeight = TOAST_PARAMS_HORIZONTAL_WEIGHT;
+ }
+ if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+ params.verticalWeight = TOAST_PARAMS_VERTICAL_WEIGHT;
+ }
+
+ mWindowManager.addView(toastView, params);
+
+ Animator inAnimator = systemUIToast.getInAnimation();
+ if (inAnimator != null) {
+ inAnimator.start();
+ }
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Animator outAnimator = systemUIToast.getOutAnimation();
+ if (outAnimator != null) {
+ outAnimator.start();
+ outAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mWindowManager.removeViewImmediate(toastView);
+ }
+ });
+ }
+ }
+ }, SHORT_DURATION_TIMEOUT);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
new file mode 100644
index 0000000..11c6980
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles.dialog
+
+import android.content.Context
+import android.os.Handler
+import android.util.Log
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+
+private const val TAG = "InternetDialogFactory"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Factory to create [InternetDialog] objects.
+ */
+@SysUISingleton
+class InternetDialogFactory @Inject constructor(
+ @Main private val handler: Handler,
+ private val internetDialogController: InternetDialogController,
+ private val context: Context,
+ private val uiEventLogger: UiEventLogger
+) {
+ companion object {
+ var internetDialog: InternetDialog? = null
+ }
+
+ /** Creates a [InternetDialog]. */
+ fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) {
+ if (internetDialog != null) {
+ if (DEBUG) {
+ Log.d(TAG, "InternetDialog is showing, do not create it twice.")
+ }
+ return
+ } else {
+ internetDialog = InternetDialog(context, this, internetDialogController,
+ canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler)
+ internetDialog?.show()
+ }
+ }
+
+ fun destroyDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "destroyDialog")
+ }
+ internetDialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java
new file mode 100644
index 0000000..6aaba99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java
@@ -0,0 +1,14 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+public class InternetDialogUtil {
+
+ public static boolean isProviderModelEnabled(Context context) {
+ if (context == null) {
+ return false;
+ }
+ return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 26781f4..2133cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -29,8 +29,8 @@
import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
@@ -187,77 +187,63 @@
* @param refreshRate Desired refresh rate
* @return array with supported width, height, and refresh rate
*/
- private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate) {
- double maxScale = 0;
+ private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate)
+ throws IOException {
+ String videoType = MediaFormat.MIMETYPE_VIDEO_AVC;
- MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- MediaCodecInfo.VideoCapabilities maxInfo = null;
- for (MediaCodecInfo codec : codecList.getCodecInfos()) {
- String videoType = MediaFormat.MIMETYPE_VIDEO_AVC;
- String[] types = codec.getSupportedTypes();
- for (String t : types) {
- if (!t.equalsIgnoreCase(videoType)) {
- continue;
- }
- MediaCodecInfo.CodecCapabilities capabilities =
- codec.getCapabilitiesForType(videoType);
- if (capabilities != null && capabilities.getVideoCapabilities() != null) {
- MediaCodecInfo.VideoCapabilities vc = capabilities.getVideoCapabilities();
+ // Get max size from the decoder, to ensure recordings will be playable on device
+ MediaCodec decoder = MediaCodec.createDecoderByType(videoType);
+ MediaCodecInfo.VideoCapabilities vc = decoder.getCodecInfo()
+ .getCapabilitiesForType(videoType).getVideoCapabilities();
+ decoder.release();
- int width = vc.getSupportedWidths().getUpper();
- int height = vc.getSupportedHeights().getUpper();
+ // Check if we can support screen size as-is
+ int width = vc.getSupportedWidths().getUpper();
+ int height = vc.getSupportedHeights().getUpper();
- int screenWidthAligned = screenWidth;
- if (screenWidthAligned % vc.getWidthAlignment() != 0) {
- screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment());
- }
- int screenHeightAligned = screenHeight;
- if (screenHeightAligned % vc.getHeightAlignment() != 0) {
- screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment());
- }
+ int screenWidthAligned = screenWidth;
+ if (screenWidthAligned % vc.getWidthAlignment() != 0) {
+ screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment());
+ }
+ int screenHeightAligned = screenHeight;
+ if (screenHeightAligned % vc.getHeightAlignment() != 0) {
+ screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment());
+ }
- if (width >= screenWidthAligned && height >= screenHeightAligned
- && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) {
- // Desired size is supported, now get the rate
- int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned,
- screenHeightAligned).getUpper().intValue();
+ if (width >= screenWidthAligned && height >= screenHeightAligned
+ && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) {
+ // Desired size is supported, now get the rate
+ int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned,
+ screenHeightAligned).getUpper().intValue();
- if (maxRate < refreshRate) {
- refreshRate = maxRate;
- }
- Log.d(TAG, "Screen size supported at rate " + refreshRate);
- return new int[]{screenWidthAligned, screenHeightAligned, refreshRate};
- }
-
- // Otherwise, continue searching
- double scale = Math.min(((double) width / screenWidth),
- ((double) height / screenHeight));
- if (scale > maxScale) {
- maxScale = Math.min(1, scale);
- maxInfo = vc;
- }
- }
+ if (maxRate < refreshRate) {
+ refreshRate = maxRate;
}
+ Log.d(TAG, "Screen size supported at rate " + refreshRate);
+ return new int[]{screenWidthAligned, screenHeightAligned, refreshRate};
}
- // Resize for max supported size
- int scaledWidth = (int) (screenWidth * maxScale);
- int scaledHeight = (int) (screenHeight * maxScale);
- if (scaledWidth % maxInfo.getWidthAlignment() != 0) {
- scaledWidth -= (scaledWidth % maxInfo.getWidthAlignment());
+ // Otherwise, resize for max supported size
+ double scale = Math.min(((double) width / screenWidth),
+ ((double) height / screenHeight));
+
+ int scaledWidth = (int) (screenWidth * scale);
+ int scaledHeight = (int) (screenHeight * scale);
+ if (scaledWidth % vc.getWidthAlignment() != 0) {
+ scaledWidth -= (scaledWidth % vc.getWidthAlignment());
}
- if (scaledHeight % maxInfo.getHeightAlignment() != 0) {
- scaledHeight -= (scaledHeight % maxInfo.getHeightAlignment());
+ if (scaledHeight % vc.getHeightAlignment() != 0) {
+ scaledHeight -= (scaledHeight % vc.getHeightAlignment());
}
// Find max supported rate for size
- int maxRate = maxInfo.getSupportedFrameRatesFor(scaledWidth, scaledHeight)
+ int maxRate = vc.getSupportedFrameRatesFor(scaledWidth, scaledHeight)
.getUpper().intValue();
if (maxRate < refreshRate) {
refreshRate = maxRate;
}
- Log.d(TAG, "Resized by " + maxScale + ": " + scaledWidth + ", " + scaledHeight
+ Log.d(TAG, "Resized by " + scale + ": " + scaledWidth + ", " + scaledHeight
+ ", " + refreshRate);
return new int[]{scaledWidth, scaledHeight, refreshRate};
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 0eaef72..31d51f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -154,6 +154,7 @@
@Override
public void onStart() {
super.onStart();
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED);
if (mPreview.getDrawable() != null) {
// We already have an image, so no need to try to load again.
@@ -245,6 +246,8 @@
}
private void onCachedImageLoaded(ImageLoader.Result imageResult) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED);
+
BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
mPreview.setImageDrawable(drawable);
mPreview.setAlpha(1f);
@@ -282,6 +285,8 @@
finish();
}
if (isFinishing()) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED);
+
if (mScrollCaptureResponse != null) {
mScrollCaptureResponse.close();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 16872b0..8def475 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -33,6 +33,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.annotation.MainThread;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -261,6 +262,7 @@
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
+ private boolean mBlockAttach;
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
@@ -559,8 +561,8 @@
mScreenshotView.reset();
}
- mScreenshotView.updateOrientation(mWindowManager.getCurrentWindowMetrics()
- .getWindowInsets().getDisplayCutout());
+ mScreenshotView.updateOrientation(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
mScreenBitmap = screenshot;
@@ -594,9 +596,8 @@
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(this::requestScrollCapture, 150);
- mScreenshotView.updateDisplayCutoutMargins(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets()
- .getDisplayCutout());
+ mScreenshotView.updateInsets(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// screenshot animation calculations won't be valid anymore, so just end
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
mScreenshotAnimation.end();
@@ -660,7 +661,7 @@
+ mLastScrollCaptureResponse.getWindowTitle() + "]");
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mScreenshotView.showScrollChip(/* onClick */ () -> {
+ mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDefaultDisplay().getRealMetrics(displayMetrics);
Bitmap newScreenshot = captureScreenshot(
@@ -732,6 +733,7 @@
new ViewTreeObserver.OnWindowAttachListener() {
@Override
public void onWindowAttached() {
+ mBlockAttach = false;
decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
action.run();
}
@@ -748,14 +750,16 @@
mWindow.setContentView(contentView);
}
+ @MainThread
private void attachWindow() {
View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow()) {
+ if (decorView.isAttachedToWindow() || mBlockAttach) {
return;
}
if (DEBUG_WINDOW) {
Log.d(TAG, "attachWindow");
}
+ mBlockAttach = true;
mWindowManager.addView(decorView, mWindowLayoutParams);
decorView.requestApplyInsets();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 5cf0188..169b28c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -71,7 +71,19 @@
@UiEvent(doc = "User has shared a long screenshot")
SCREENSHOT_LONG_SCREENSHOT_SHARE(689),
@UiEvent(doc = "User has sent a long screenshot to the editor")
- SCREENSHOT_LONG_SCREENSHOT_EDIT(690);
+ SCREENSHOT_LONG_SCREENSHOT_EDIT(690),
+ @UiEvent(doc = "A long screenshot capture has started")
+ SCREENSHOT_LONG_SCREENSHOT_STARTED(880),
+ @UiEvent(doc = "The long screenshot capture failed")
+ SCREENSHOT_LONG_SCREENSHOT_FAILURE(881),
+ @UiEvent(doc = "The long screenshot capture completed successfully")
+ SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882),
+ @UiEvent(doc = "Long screenshot editor activity started")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED(889),
+ @UiEvent(doc = "Long screenshot editor activity loaded a previously saved screenshot")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED(890),
+ @UiEvent(doc = "Long screenshot editor activity finished")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED(891);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e9e62f2..dfb39e3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -118,8 +118,8 @@
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183;
+ private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350;
+ private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350;
private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final float ROUNDED_CORNER_RADIUS = .25f;
@@ -242,19 +242,21 @@
/**
* Called to display the scroll action chip when support is detected.
*
+ * @param packageName the owning package of the window to be captured
* @param onClick the action to take when the chip is clicked.
*/
- public void showScrollChip(Runnable onClick) {
+ public void showScrollChip(String packageName, Runnable onClick) {
if (DEBUG_SCROLL) {
Log.d(TAG, "Showing Scroll option");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION);
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
mScrollChip.setVisibility(VISIBLE);
mScrollChip.setOnClickListener((v) -> {
if (DEBUG_INPUT) {
Log.d(TAG, "scroll chip tapped");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED);
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ packageName);
onClick.run();
});
}
@@ -414,21 +416,30 @@
mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
}
- void updateDisplayCutoutMargins(DisplayCutout cutout) {
+ void updateInsets(WindowInsets insets) {
int orientation = mContext.getResources().getConfiguration().orientation;
mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
FrameLayout.LayoutParams p =
(FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams();
+ DisplayCutout cutout = insets.getDisplayCutout();
+ Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
if (cutout == null) {
- p.setMargins(0, 0, 0, 0);
+ p.setMargins(0, 0, 0, navBarInsets.bottom);
} else {
Insets waterfall = cutout.getWaterfallInsets();
if (mOrientationPortrait) {
- p.setMargins(waterfall.left, Math.max(cutout.getSafeInsetTop(), waterfall.top),
- waterfall.right, Math.max(cutout.getSafeInsetBottom(), waterfall.bottom));
+ p.setMargins(
+ waterfall.left,
+ Math.max(cutout.getSafeInsetTop(), waterfall.top),
+ waterfall.right,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
} else {
- p.setMargins(Math.max(cutout.getSafeInsetLeft(), waterfall.left), waterfall.top,
- Math.max(cutout.getSafeInsetRight(), waterfall.right), waterfall.bottom);
+ p.setMargins(
+ Math.max(cutout.getSafeInsetLeft(), waterfall.left),
+ waterfall.top,
+ Math.max(cutout.getSafeInsetRight(), waterfall.right),
+ Math.max(navBarInsets.bottom, waterfall.bottom));
}
}
mStaticLeftMargin = p.leftMargin;
@@ -436,10 +447,10 @@
mScreenshotStatic.requestLayout();
}
- void updateOrientation(DisplayCutout cutout) {
+ void updateOrientation(WindowInsets insets) {
int orientation = mContext.getResources().getConfiguration().orientation;
mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
- updateDisplayCutoutMargins(cutout);
+ updateInsets(insets);
int screenshotFixedSize =
mContext.getResources().getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
@@ -978,7 +989,6 @@
mScrollingScrim.setVisibility(View.GONE);
mScrollablePreview.setVisibility(View.GONE);
mScreenshotStatic.setTranslationX(0);
- mScreenshotPreview.setTranslationY(0);
mScreenshotPreview.setContentDescription(
mContext.getResources().getString(R.string.screenshot_preview_description));
mScreenshotPreview.setOnClickListener(null);
@@ -994,9 +1004,6 @@
mSmartChips.clear();
mQuickShareChip = null;
setAlpha(1);
- mDismissButton.setTranslationY(0);
- mActionsContainer.setTranslationY(0);
- mActionsContainerBackground.setTranslationY(0);
mScreenshotSelectorView.stop();
}
@@ -1024,22 +1031,19 @@
setAlpha(1 - animation.getAnimatedFraction());
});
- ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
- yAnim.setInterpolator(mAccelerateInterpolator);
- yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
- float screenshotStartY = mScreenshotPreview.getTranslationY();
- float dismissStartY = mDismissButton.getTranslationY();
- yAnim.addUpdateListener(animation -> {
- float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
- mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
- mScreenshotPreviewBorder.setTranslationY(screenshotStartY + yDelta);
- mDismissButton.setTranslationY(dismissStartY + yDelta);
- mActionsContainer.setTranslationY(yDelta);
- mActionsContainerBackground.setTranslationY(yDelta);
+ ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1);
+ xAnim.setInterpolator(mAccelerateInterpolator);
+ xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
+ float deltaX = mDirectionLTR
+ ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
+ : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
+ xAnim.addUpdateListener(animation -> {
+ float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
+ mScreenshotStatic.setTranslationX(currXDelta);
});
AnimatorSet animSet = new AnimatorSet();
- animSet.play(yAnim).with(alphaAnim);
+ animSet.play(xAnim).with(alphaAnim);
return animSet;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 6dc6874..ef7355a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -28,6 +28,7 @@
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -61,6 +62,7 @@
private final Context mContext;
private final Executor mBgExecutor;
private final ImageTileSet mImageTileSet;
+ private final UiEventLogger mEventLogger;
private final ScrollCaptureClient mClient;
private Completer<LongScreenshot> mCaptureCompleter;
@@ -69,6 +71,7 @@
private Session mSession;
private ListenableFuture<CaptureResult> mTileFuture;
private ListenableFuture<Void> mEndFuture;
+ private String mWindowOwner;
static class LongScreenshot {
private final ImageTileSet mImageTileSet;
@@ -135,11 +138,12 @@
@Inject
ScrollCaptureController(Context context, @Background Executor bgExecutor,
- ScrollCaptureClient client, ImageTileSet imageTileSet) {
+ ScrollCaptureClient client, ImageTileSet imageTileSet, UiEventLogger logger) {
mContext = context;
mBgExecutor = bgExecutor;
mClient = client;
mImageTileSet = imageTileSet;
+ mEventLogger = logger;
}
@VisibleForTesting
@@ -157,6 +161,7 @@
ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
return CallbackToFutureAdapter.getFuture(completer -> {
mCaptureCompleter = completer;
+ mWindowOwner = response.getPackageName();
mBgExecutor.execute(() -> {
float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
@@ -173,11 +178,13 @@
if (LogConfig.DEBUG_SCROLL) {
Log.d(TAG, "got session " + mSession);
}
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_STARTED, 0, mWindowOwner);
requestNextTile(0);
} catch (InterruptedException | ExecutionException e) {
// Failure to start, propagate to caller
Log.e(TAG, "session start failed!");
mCaptureCompleter.setException(e);
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
}
}
@@ -297,6 +304,11 @@
if (LogConfig.DEBUG_SCROLL) {
Log.d(TAG, "finishCapture()");
}
+ if (mImageTileSet.getHeight() > 0) {
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_COMPLETED, 0, mWindowOwner);
+ } else {
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
+ }
mEndFuture = mSession.end();
mEndFuture.addListener(() -> {
if (LogConfig.DEBUG_SCROLL) {
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
new file mode 100644
index 0000000..c50365f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
@@ -0,0 +1,74 @@
+package com.android.systemui.sensorprivacy
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.res.Resources
+import android.text.Html
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.ImageView
+import com.android.internal.widget.DialogTitle
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+class SensorUseDialog(
+ context: Context,
+ val sensor: Int,
+ val clickListener: DialogInterface.OnClickListener
+) : SystemUIDialog(context) {
+
+ // TODO move to onCreate (b/200815309)
+ init {
+ window!!.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+ window!!.addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+
+ val layoutInflater = LayoutInflater.from(context)
+ val customTitleView = layoutInflater.inflate(R.layout.sensor_use_started_title, null)
+ customTitleView.requireViewById<DialogTitle>(R.id.sensor_use_started_title_message)
+ .setText(when (sensor) {
+ SensorUseStartedActivity.MICROPHONE ->
+ R.string.sensor_privacy_start_use_mic_dialog_title
+ SensorUseStartedActivity.CAMERA ->
+ R.string.sensor_privacy_start_use_camera_dialog_title
+ SensorUseStartedActivity.ALL_SENSORS ->
+ R.string.sensor_privacy_start_use_mic_camera_dialog_title
+ else -> Resources.ID_NULL
+ })
+ customTitleView.requireViewById<ImageView>(R.id.sensor_use_microphone_icon).visibility =
+ if (sensor == SensorUseStartedActivity.MICROPHONE ||
+ sensor == SensorUseStartedActivity.ALL_SENSORS) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+ customTitleView.requireViewById<ImageView>(R.id.sensor_use_camera_icon).visibility =
+ if (sensor == SensorUseStartedActivity.CAMERA ||
+ sensor == SensorUseStartedActivity.ALL_SENSORS) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+
+ setCustomTitle(customTitleView)
+ setMessage(Html.fromHtml(context.getString(when (sensor) {
+ SensorUseStartedActivity.MICROPHONE ->
+ R.string.sensor_privacy_start_use_mic_dialog_content
+ SensorUseStartedActivity.CAMERA ->
+ R.string.sensor_privacy_start_use_camera_dialog_content
+ SensorUseStartedActivity.ALL_SENSORS ->
+ R.string.sensor_privacy_start_use_mic_camera_dialog_content
+ else -> Resources.ID_NULL
+ }), 0))
+
+ setButton(BUTTON_POSITIVE,
+ context.getString(com.android.internal.R.string
+ .sensor_privacy_start_use_dialog_turn_on_button), clickListener)
+ setButton(BUTTON_NEGATIVE,
+ context.getString(com.android.internal.R.string
+ .cancel), clickListener)
+
+ setCancelable(false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index f0fb5eb..b0071d9 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -16,33 +16,28 @@
package com.android.systemui.sensorprivacy
+import android.app.Activity
+import android.app.AlertDialog
import android.content.DialogInterface
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_POSITIVE
import android.content.Intent
import android.content.Intent.EXTRA_PACKAGE_NAME
-import android.content.pm.PackageManager
-import android.content.res.Resources
import android.hardware.SensorPrivacyManager
import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS
import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
import android.hardware.SensorPrivacyManager.Sources.DIALOG
import android.os.Bundle
import android.os.Handler
-import android.text.Html
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.widget.ImageView
-import com.android.internal.app.AlertActivity
-import com.android.internal.widget.DialogTitle
-import com.android.systemui.R
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
+import com.android.internal.util.FrameworkStatsLog.write
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
-import com.android.internal.util.FrameworkStatsLog.write
/**
* Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is
@@ -55,7 +50,7 @@
private val keyguardStateController: KeyguardStateController,
private val keyguardDismissUtil: KeyguardDismissUtil,
@Background private val bgHandler: Handler
-) : AlertActivity(), DialogInterface.OnClickListener {
+) : Activity(), DialogInterface.OnClickListener {
companion object {
private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
@@ -63,9 +58,9 @@
private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L
private const val UNLOCK_DELAY_MILLIS = 200L
- private const val CAMERA = SensorPrivacyManager.Sensors.CAMERA
- private const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE
- private const val ALL_SENSORS = Integer.MAX_VALUE
+ internal const val CAMERA = SensorPrivacyManager.Sensors.CAMERA
+ internal const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE
+ internal const val ALL_SENSORS = Integer.MAX_VALUE
}
private var sensor = -1
@@ -74,6 +69,8 @@
private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback
+ private var mDialog: AlertDialog? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -91,7 +88,7 @@
IndividualSensorPrivacyController.Callback { _, _ ->
if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
!sensorPrivacyController.isSensorBlocked(CAMERA)) {
- dismiss()
+ finish()
}
}
@@ -109,71 +106,22 @@
}
}
sensorPrivacyListener =
- IndividualSensorPrivacyController.Callback {
- whichSensor: Int, isBlocked: Boolean ->
+ IndividualSensorPrivacyController.Callback { whichSensor: Int,
+ isBlocked: Boolean ->
if (whichSensor == sensor && !isBlocked) {
- dismiss()
+ finish()
}
}
sensorPrivacyController.addCallback(sensorPrivacyListener)
- sensorPrivacyController.addCallback { _, isBlocked ->
- if (!isBlocked) {
- dismiss()
- }
- }
- }
-
- mAlertParams.apply {
- try {
- mCustomTitleView = mInflater.inflate(R.layout.sensor_use_started_title, null)
- mCustomTitleView.findViewById<DialogTitle>(R.id.sensor_use_started_title_message)!!
- .setText(when (sensor) {
- MICROPHONE ->
- R.string.sensor_privacy_start_use_mic_dialog_title
- CAMERA ->
- R.string.sensor_privacy_start_use_camera_dialog_title
- ALL_SENSORS ->
- R.string.sensor_privacy_start_use_mic_camera_dialog_title
- else -> Resources.ID_NULL
- })
-
- mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_microphone_icon)!!
- .visibility = if (sensor == MICROPHONE || sensor == ALL_SENSORS) {
- VISIBLE
- } else {
- GONE
- }
- mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_camera_icon)!!
- .visibility = if (sensor == CAMERA || sensor == ALL_SENSORS) {
- VISIBLE
- } else {
- GONE
- }
-
- mMessage = Html.fromHtml(getString(when (sensor) {
- MICROPHONE ->
- R.string.sensor_privacy_start_use_mic_dialog_content
- CAMERA ->
- R.string.sensor_privacy_start_use_camera_dialog_content
- ALL_SENSORS ->
- R.string.sensor_privacy_start_use_mic_camera_dialog_content
- else -> Resources.ID_NULL
- }, packageManager.getApplicationInfo(sensorUsePackageName, 0)
- .loadLabel(packageManager)), 0)
- } catch (e: PackageManager.NameNotFoundException) {
+ if (!sensorPrivacyController.isSensorBlocked(sensor)) {
finish()
return
}
-
- mPositiveButtonText = getString(
- com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button)
- mNegativeButtonText = getString(android.R.string.cancel)
- mPositiveButtonListener = this@SensorUseStartedActivity
- mNegativeButtonListener = this@SensorUseStartedActivity
}
- setupAlert()
+ mDialog = SensorUseDialog(this, sensor, this)
+ mDialog!!.show()
}
override fun onStart() {
@@ -212,7 +160,7 @@
}
}
- dismiss()
+ finish()
}
override fun onStop() {
@@ -229,6 +177,7 @@
override fun onDestroy() {
super.onDestroy()
+ mDialog?.dismiss()
sensorPrivacyController.removeCallback(sensorPrivacyListener)
}
@@ -263,4 +212,4 @@
.suppressSensorPrivacyReminders(sensor, suppressed)
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index 8cd3632..cc5cf4b 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.util.Log;
import android.view.View;
+import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -57,6 +58,8 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index dce19cf..cfbe3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -54,22 +54,22 @@
/**
* Translates a ratio from 0 to 1 to a blur radius in pixels.
*/
- fun blurRadiusOfRatio(ratio: Float): Int {
+ fun blurRadiusOfRatio(ratio: Float): Float {
if (ratio == 0f) {
- return 0
+ return 0f
}
- return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio).toInt()
+ return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio)
}
/**
* Translates a blur radius in pixels to a ratio between 0 to 1.
*/
- fun ratioOfBlurRadius(blur: Int): Float {
- if (blur == 0) {
+ fun ratioOfBlurRadius(blur: Float): Float {
+ if (blur == 0f) {
return 0f
}
return MathUtils.map(minBlurRadius.toFloat(), maxBlurRadius.toFloat(),
- 0f /* maxStart */, 1f /* maxStop */, blur.toFloat())
+ 0f /* maxStart */, 1f /* maxStop */, blur)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 8e52b0d..50911d16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -290,8 +290,8 @@
default void showAuthenticationDialog(PromptInfo promptInfo,
IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed,
- boolean requireConfirmation, int userId, String opPackageName,
- long operationId, @BiometricMultiSensorMode int multiSensorConfig) {
+ boolean requireConfirmation, int userId, long operationId, String opPackageName,
+ long requestId, @BiometricMultiSensorMode int multiSensorConfig) {
}
/** @see IStatusBar#onBiometricAuthenticated() */
@@ -843,7 +843,7 @@
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
@@ -855,6 +855,7 @@
args.argi1 = userId;
args.arg6 = opPackageName;
args.arg7 = operationId;
+ args.arg8 = requestId;
args.argi2 = multiSensorConfig;
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
.sendToTarget();
@@ -1312,8 +1313,9 @@
(boolean) someArgs.arg4 /* credentialAllowed */,
(boolean) someArgs.arg5 /* requireConfirmation */,
someArgs.argi1 /* userId */,
- (String) someArgs.arg6 /* opPackageName */,
(long) someArgs.arg7 /* operationId */,
+ (String) someArgs.arg6 /* opPackageName */,
+ (long) someArgs.arg8 /* requestId */,
someArgs.argi2 /* multiSensorConfig */);
}
someArgs.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 503b5c0..8a39719 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -66,6 +66,7 @@
import com.android.internal.widget.ViewClippingUtil;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -334,7 +335,7 @@
info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
}
}
- if (info != null) {
+ if (!TextUtils.isEmpty(info)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_OWNER_INFO,
new KeyguardIndication.Builder()
@@ -432,7 +433,7 @@
}
private void updateResting() {
- if (mRestingIndication != null
+ if (!TextUtils.isEmpty(mRestingIndication)
&& !mRotateTextViewController.hasIndications()) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_RESTING,
@@ -455,7 +456,8 @@
new KeyguardIndication.Builder()
.setMessage(mContext.getResources().getString(
com.android.internal.R.string.global_action_logout))
- .setTextColor(mInitialTextColorState)
+ .setTextColor(Utils.getColorAttr(
+ mContext, com.android.internal.R.attr.textColorOnAccent))
.setBackground(mContext.getDrawable(
com.android.systemui.R.drawable.logout_button_background))
.setClickListener((view) -> {
@@ -723,15 +725,13 @@
}
protected String computePowerIndication() {
- if (mPowerCharged) {
- return mContext.getResources().getString(R.string.keyguard_charged);
- }
-
int chargingId;
- String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
if (mBatteryOverheated) {
chargingId = R.string.keyguard_plugged_in_charging_limited;
+ String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(chargingId, percentage);
+ } else if (mPowerCharged) {
+ return mContext.getResources().getString(R.string.keyguard_charged);
}
final boolean hasChargingTime = mChargingTimeRemaining > 0;
@@ -759,6 +759,7 @@
: R.string.keyguard_plugged_in_wireless;
}
+ String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
if (hasChargingTime) {
String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
mContext, mChargingTimeRemaining);
@@ -799,7 +800,9 @@
* Show message on the keyguard for how the user can unlock/enter their device.
*/
public void showActionToUnlock() {
- if (mDozing) {
+ if (mDozing
+ && !mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+ KeyguardUpdateMonitor.getCurrentUser())) {
return;
}
@@ -810,18 +813,13 @@
String message = mContext.getString(R.string.keyguard_retry);
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 */);
- }
+ } else {
+ showTransientIndication(mContext.getString(R.string.keyguard_unlock),
+ false /* isError */, true /* hideOnScreenOff */);
}
}
- private void showTryFingerprintMsg() {
+ private void showTryFingerprintMsg(String a11yString) {
if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
// if udfps available, there will always be a tappable affordance to unlock
// For example, the lock icon
@@ -833,6 +831,11 @@
} else {
showTransientIndication(R.string.keyguard_try_fingerprint);
}
+
+ // Although we suppress face auth errors visually, we still announce them for a11y
+ if (!TextUtils.isEmpty(a11yString)) {
+ mLockScreenIndicationView.announceForAccessibility(a11yString);
+ }
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -913,7 +916,7 @@
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
if (biometricSourceType == BiometricSourceType.FACE
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(helpString);
return;
}
showTransientIndication(helpString, false /* isError */, showActionToUnlock);
@@ -933,7 +936,7 @@
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()
&& !mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isScreenOn()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -942,7 +945,7 @@
if (!mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isUdfpsEnrolled()
&& mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(errString);
} else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
mStatusBarKeyguardViewManager.showBouncerMessage(
mContext.getResources().getString(R.string.keyguard_unlock_press),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 538db61..21ed9da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -149,7 +149,10 @@
*/
class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- lateinit var revealAmountListener: Consumer<Float>
+ /**
+ * Listener that is called if the scrim's opaqueness changes
+ */
+ lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
/**
* How much of the underlying views are revealed, in percent. 0 means they will be completely
@@ -161,7 +164,7 @@
field = value
revealEffect.setRevealAmountOnScrim(value, this)
- revealAmountListener.accept(value)
+ updateScrimOpaque()
invalidate()
}
}
@@ -201,6 +204,31 @@
}
/**
+ * Is the scrim currently fully opaque
+ */
+ var isScrimOpaque = false
+ private set(value) {
+ if (field != value) {
+ field = value
+ isScrimOpaqueChangedListener.accept(field)
+ }
+ }
+
+ private fun updateScrimOpaque() {
+ isScrimOpaque = revealAmount == 0.0f && alpha == 1.0f && visibility == VISIBLE
+ }
+
+ override fun setAlpha(alpha: Float) {
+ super.setAlpha(alpha)
+ updateScrimOpaque()
+ }
+
+ override fun setVisibility(visibility: Int) {
+ super.setVisibility(visibility)
+ updateScrimOpaque()
+ }
+
+ /**
* Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated
* via local matrix in [onDraw] so we never need to construct a new shader.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 002c9c7..2a8771e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -68,7 +68,7 @@
private const val VELOCITY_SCALE = 100f
private const val MAX_VELOCITY = 3000f
private const val MIN_VELOCITY = -MAX_VELOCITY
- private const val INTERACTION_BLUR_FRACTION = 0.4f
+ private const val INTERACTION_BLUR_FRACTION = 0.8f
private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION
private const val TAG = "DepthController"
}
@@ -78,7 +78,8 @@
private var keyguardAnimator: Animator? = null
private var notificationAnimator: Animator? = null
private var updateScheduled: Boolean = false
- private var shadeExpansion = 0f
+ @VisibleForTesting
+ var shadeExpansion = 0f
private var isClosed: Boolean = true
private var isOpen: Boolean = false
private var isBlurred: Boolean = false
@@ -92,8 +93,9 @@
// Only for dumpsys
private var lastAppliedBlur = 0
- @VisibleForTesting
- var shadeSpring = DepthAnimation()
+ // Shade expansion offset that happens when pulling down on a HUN.
+ var panelPullDownMinFraction = 0f
+
var shadeAnimation = DepthAnimation()
@VisibleForTesting
@@ -101,12 +103,16 @@
var brightnessMirrorVisible: Boolean = false
set(value) {
field = value
- brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f)
+ brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f).toInt()
else 0)
}
var qsPanelExpansion = 0f
set(value) {
+ if (value.isNaN()) {
+ Log.w(TAG, "Invalid qs expansion")
+ return
+ }
if (field == value) return
field = value
scheduleUpdate()
@@ -134,15 +140,13 @@
field = value
scheduleUpdate()
- if (shadeSpring.radius == 0 && shadeAnimation.radius == 0) {
+ if (shadeExpansion == 0f && shadeAnimation.radius == 0f) {
return
}
// Do not remove blurs when we're re-enabling them
if (!value) {
return
}
- shadeSpring.animateTo(0)
- shadeSpring.finishIfRunning()
shadeAnimation.animateTo(0)
shadeAnimation.finishIfRunning()
@@ -161,7 +165,7 @@
/**
* Blur radius of the wake-up animation on this frame.
*/
- private var wakeAndUnlockBlurRadius = 0
+ private var wakeAndUnlockBlurRadius = 0f
set(value) {
if (field == value) return
field = value
@@ -174,26 +178,31 @@
@VisibleForTesting
val updateBlurCallback = Choreographer.FrameCallback {
updateScheduled = false
- val normalizedBlurRadius = MathUtils.constrain(shadeAnimation.radius,
- blurUtils.minBlurRadius, blurUtils.maxBlurRadius)
- var combinedBlur = (shadeSpring.radius * INTERACTION_BLUR_FRACTION +
- normalizedBlurRadius * ANIMATION_BLUR_FRACTION).toInt()
- val qsExpandedRatio = qsPanelExpansion * shadeExpansion
+ val animationRadius = MathUtils.constrain(shadeAnimation.radius,
+ blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat())
+ val expansionRadius = blurUtils.blurRadiusOfRatio(
+ Interpolators.getNotificationScrimAlpha(
+ if (shouldApplyShadeBlur()) shadeExpansion else 0f, false))
+ var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION +
+ animationRadius * ANIMATION_BLUR_FRACTION)
+ 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).toFloat()
+ var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
if (blursDisabledForAppLaunch) {
shadeRadius = 0f
}
+ var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(shadeRadius))
var blur = shadeRadius.toInt()
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
blur = 0
+ zoomOut = 0f
}
- val zoomOut = blurUtils.ratioOfBlurRadius(blur)
if (!blurUtils.supportsBlursOnWindows()) {
blur = 0
@@ -266,12 +275,11 @@
override fun onStateChanged(newState: Int) {
updateShadeAnimationBlur(
shadeExpansion, prevTracking, prevShadeVelocity, prevShadeDirection)
- updateShadeBlur()
+ scheduleUpdate()
}
override fun onDozingChanged(isDozing: Boolean) {
if (isDozing) {
- shadeSpring.finishIfRunning()
shadeAnimation.finishIfRunning()
brightnessMirrorSpring.finishIfRunning()
}
@@ -308,8 +316,10 @@
/**
* Update blurs when pulling down the shade
*/
- override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
+ override fun onPanelExpansionChanged(rawExpansion: Float, tracking: Boolean) {
val timestamp = SystemClock.elapsedRealtimeNanos()
+ val expansion = MathUtils.saturate(
+ (rawExpansion - panelPullDownMinFraction) / (1f - panelPullDownMinFraction))
if (shadeExpansion == expansion && prevTracking == tracking) {
prevTimestamp = timestamp
@@ -336,7 +346,7 @@
prevTracking = tracking
prevTimestamp = timestamp
- updateShadeBlur()
+ scheduleUpdate()
}
private fun updateShadeAnimationBlur(
@@ -399,15 +409,7 @@
}
shadeAnimation.setStartVelocity(velocity)
- shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized))
- }
-
- private fun updateShadeBlur() {
- var newBlur = 0
- if (shouldApplyShadeBlur()) {
- newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
- }
- shadeSpring.animateTo(newBlur)
+ shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized).toInt())
}
private fun scheduleUpdate(viewToBlur: View? = null) {
@@ -433,7 +435,8 @@
IndentingPrintWriter(pw, " ").let {
it.println("StatusBarWindowBlurController:")
it.increaseIndent()
- it.println("shadeRadius: ${shadeSpring.radius}")
+ it.println("shadeExpansion: $shadeExpansion")
+ it.println("shouldApplyShaeBlur: ${shouldApplyShadeBlur()}")
it.println("shadeAnimation: ${shadeAnimation.radius}")
it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
@@ -452,7 +455,7 @@
/**
* Blur radius visible on the UI, in pixels.
*/
- var radius = 0
+ var radius = 0f
/**
* Depth ratio of the current blur radius.
@@ -473,12 +476,12 @@
private var springAnimation = SpringAnimation(this, object :
FloatPropertyCompat<DepthAnimation>("blurRadius") {
override fun setValue(rect: DepthAnimation?, value: Float) {
- radius = value.toInt()
+ radius = value
scheduleUpdate(view)
}
override fun getValue(rect: DepthAnimation?): Float {
- return radius.toFloat()
+ return radius
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index f0d779c..6ea79af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -182,10 +182,10 @@
default void setFaceAuthDisplayBrightness(float brightness) {}
/**
- * How much {@link LightRevealScrim} obscures the UI.
- * @param amount 0 when opaque, 1 when not transparent
+ * If {@link LightRevealScrim} obscures the UI.
+ * @param opaque if the scrim is opaque
*/
- default void setLightRevealScrimAmount(float amount) {}
+ default void setLightRevealScrimOpaque(boolean opaque) {}
/**
* Custom listener to pipe data back to plugins about whether or not the status bar would be
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
index 4a467ce..d01fc93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -104,7 +104,7 @@
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 1.5f
+ (1 - rippleShader.progress)) * radius * 2
canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
index 146046b..5175977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
@@ -147,8 +147,12 @@
val fadeIn = subProgress(0f, 0.1f, value)
val fadeOutNoise = subProgress(0.4f, 1f, value)
- val fadeOutRipple = subProgress(0.3f, 1f, value)
- val fadeCircle = subProgress(0f, 0.2f, value)
+ var fadeOutRipple = 0f
+ var fadeCircle = 0f
+ if (shouldFadeOutRipple) {
+ fadeCircle = subProgress(0f, 0.2f, value)
+ fadeOutRipple = subProgress(0.3f, 1f, value)
+ }
setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
setUniform("in_fadeCircle", 1 - fadeCircle)
setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
@@ -200,4 +204,6 @@
field = value
setUniform("in_pixelDensity", value)
}
+
+ var shouldFadeOutRipple: Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 71546ae..0773460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -24,7 +24,6 @@
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
-import android.content.pm.UserInfo
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
@@ -45,6 +44,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
import java.lang.RuntimeException
@@ -67,6 +67,7 @@
private val contentResolver: ContentResolver,
private val configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
+ private val deviceProvisionedController: DeviceProvisionedController,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Main private val handler: Handler,
@@ -83,6 +84,55 @@
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
+ private val deviceProvisionedListener =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onDeviceProvisionedChanged() {
+ connectSession()
+ }
+
+ override fun onUserSetupChanged() {
+ connectSession()
+ }
+ }
+
+ private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
+ execution.assertIsMainThread()
+ val filteredTargets = targets.filter(::filterSmartspaceTarget)
+ plugin?.onTargetsAvailable(filteredTargets)
+ }
+
+ private val userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val settingsObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val configChangeListener = object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ execution.assertIsMainThread()
+ updateTextColorFromWallpaper()
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ execution.assertIsMainThread()
+ smartspaceView.setDozeAmount(eased)
+ }
+ }
+
+ init {
+ deviceProvisionedController.addCallback(deviceProvisionedListener)
+ }
+
fun isEnabled(): Boolean {
execution.assertIsMainThread()
@@ -141,13 +191,23 @@
}
private fun connectSession() {
- if (plugin == null || session != null) {
+ if (plugin == null || session != null || !this::smartspaceView.isInitialized) {
return
}
- val session = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, "lockscreen").build())
- session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ // Only connect after the device is fully provisioned to avoid connection caching
+ // issues
+ if (!deviceProvisionedController.isDeviceProvisioned() ||
+ !deviceProvisionedController.isCurrentUserSetup()) {
+ return
+ }
+
+ val newSession = smartspaceManager.createSmartspaceSession(
+ SmartspaceConfig.Builder(context, "lockscreen").build())
+ newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ this.session = newSession
+
+ deviceProvisionedController.removeCallback(deviceProvisionedListener)
userTracker.addCallback(userTrackerCallback, uiExecutor)
contentResolver.registerContentObserver(
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -158,8 +218,6 @@
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
- this.session = session
-
reloadSmartspace()
}
@@ -198,43 +256,6 @@
plugin?.unregisterListener(listener)
}
- private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
- execution.assertIsMainThread()
- val filteredTargets = targets.filter(::filterSmartspaceTarget)
- plugin?.onTargetsAvailable(filteredTargets)
- }
-
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
-
- override fun onProfilesChanged(profiles: List<UserInfo>) {
- }
- }
-
- private val settingsObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
- }
-
- private val configChangeListener = object : ConfigurationController.ConfigurationListener {
- override fun onThemeChanged() {
- execution.assertIsMainThread()
- updateTextColorFromWallpaper()
- }
- }
-
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- execution.assertIsMainThread()
- smartspaceView.setDozeAmount(eased)
- }
- }
-
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
return when (t.userHandle) {
userTracker.userHandle -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index dba3401..9c755e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -85,23 +85,26 @@
}
/**
- * Set the content of this view to be visible in an animated way.
- *
- * @param contentVisible True if the content should be visible or false if it should be hidden.
+ * @param visible True if we should animate contents visible
*/
- public void setContentVisible(boolean contentVisible) {
- setContentVisible(contentVisible, true /* animate */);
+ public void setContentVisible(boolean visible) {
+ setContentVisible(visible, true /* animate */, null /* runAfter */);
}
+
/**
- * Set the content of this view to be visible.
- * @param contentVisible True if the content should be visible or false if it should be hidden.
- * @param animate Should an animation be performed.
+ * @param visible True if the contents should be visible
+ * @param animate True if we should fade to new visibility
+ * @param runAfter Runnable to run after visibility updates
*/
- private void setContentVisible(boolean contentVisible, boolean animate) {
- if (mContentVisible != contentVisible) {
+ public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) {
+ if (mContentVisible != visible) {
mContentAnimating = animate;
- mContentVisible = contentVisible;
- setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
+ mContentVisible = visible;
+ Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> {
+ mContentVisibilityEndRunnable.run();
+ runAfter.run();
+ };
+ setViewVisible(mContent, visible, animate, endRunnable);
}
if (!mContentAnimating) {
@@ -113,6 +116,10 @@
return mContentVisible;
}
+ public void setVisible(boolean nowVisible, boolean animate) {
+ setVisible(nowVisible, animate, null);
+ }
+
/**
* Make this view visible. If {@code false} is passed, the view will fade out it's content
* and set the view Visibility to GONE. If only the content should be changed
@@ -121,7 +128,7 @@
* @param nowVisible should the view be visible
* @param animate should the change be animated.
*/
- public void setVisible(boolean nowVisible, boolean animate) {
+ public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) {
if (mIsVisible != nowVisible) {
mIsVisible = nowVisible;
if (animate) {
@@ -132,10 +139,10 @@
} else {
setWillBeGone(true);
}
- setContentVisible(nowVisible, true /* animate */);
+ setContentVisible(nowVisible, true /* animate */, runAfter);
} else {
setVisibility(nowVisible ? VISIBLE : GONE);
- setContentVisible(nowVisible, false /* animate */);
+ setContentVisible(nowVisible, false /* animate */, runAfter);
setWillBeGone(false);
notifyHeightChanged(false /* needsAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 23aefd9..594afce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -46,6 +46,7 @@
private Runnable mRoundingChangedCallback;
private ExpandableNotificationRow mTrackedHeadsUp;
private float mAppearFraction;
+ private boolean mIsDismissAllInProgress;
private ExpandableView mSwipedView = null;
private ExpandableView mViewBeforeSwipedView = null;
@@ -162,6 +163,10 @@
}
}
+ void setDismissAllInProgress(boolean isClearingAll) {
+ mIsDismissAllInProgress = isClearingAll;
+ }
+
private float getRoundness(ExpandableView view, boolean top) {
if (view == null) {
return 0f;
@@ -171,6 +176,11 @@
|| view == mViewAfterSwipedView) {
return 1f;
}
+ if (view instanceof ExpandableNotificationRow
+ && ((ExpandableNotificationRow) view).canViewBeDismissed()
+ && mIsDismissAllInProgress) {
+ return 1.0f;
+ }
if ((view.isPinned()
|| (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
return 1.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 0660daa..733c0a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -144,6 +144,10 @@
private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean(
"persist.debug.nssl.dismiss", false /* default */);
+ // Delay in milli-seconds before shade closes for clear all.
+ private final int DELAY_BEFORE_SHADE_CLOSE = 200;
+ private boolean mShadeNeedsToClose = false;
+
private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
@@ -256,10 +260,10 @@
private boolean mExpandedInThisMotion;
private boolean mShouldShowShelfOnly;
protected boolean mScrollingEnabled;
+ private boolean mIsCurrentUserSetup;
protected FooterView mFooterView;
protected EmptyShadeView mEmptyShadeView;
private boolean mDismissAllInProgress;
- private boolean mFadeNotificationsOnDismiss;
private FooterDismissListener mFooterDismissListener;
private boolean mFlingAfterUpEvent;
@@ -612,6 +616,7 @@
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
mGroupMembershipManager = groupMembershipManager;
mGroupExpansionManager = groupExpansionManager;
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) {
@@ -683,6 +688,7 @@
mController.hasActiveClearableNotifications(ROWS_ALL);
RemoteInputController remoteInputController = mRemoteInputManager.getController();
boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0)
+ && mIsCurrentUserSetup // see: b/193149550
&& mStatusBarState != StatusBarState.KEYGUARD
&& !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
&& (remoteInputController == null || !remoteInputController.isRemoteInputActive());
@@ -1192,7 +1198,7 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void clampScrollPosition() {
int scrollRange = getScrollRange();
- if (scrollRange < mOwnScrollY) {
+ if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) {
boolean animateStackY = false;
if (scrollRange < getScrollAmountToScrollBoundary()
&& mAnimateStackYForContentHeightChange) {
@@ -1708,6 +1714,11 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
+ if (child instanceof SectionHeaderView) {
+ ((StackScrollerDecorView) child).setContentVisible(
+ false /* visible */, true /* animate */, endRunnable);
+ return;
+ }
mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
true /* isDismissAll */);
}
@@ -4050,6 +4061,19 @@
runAnimationFinishedRunnables();
clearTransient();
clearHeadsUpDisappearRunning();
+
+ if (mAmbientState.isDismissAllInProgress()) {
+ setDismissAllInProgress(false);
+
+ if (mShadeNeedsToClose) {
+ mShadeNeedsToClose = false;
+ postDelayed(
+ () -> {
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ },
+ DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
+ }
+ }
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4407,6 +4431,7 @@
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
mAmbientState.setDismissAllInProgress(dismissAllInProgress);
+ mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress);
handleDismissAllClipping();
}
@@ -4947,64 +4972,129 @@
mHeadsUpAppearanceController = headsUpAppearanceController;
}
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- @VisibleForTesting
- void clearNotifications(@SelectedRows int selection, boolean closeShade) {
- // animate-swipe all dismissable notifications, then animate the shade closed
- int numChildren = getChildCount();
+ private boolean isVisible(View child) {
+ boolean hasClipBounds = child.getClipBounds(mTmpRect);
+ return child.getVisibility() == View.VISIBLE
+ && (!hasClipBounds || mTmpRect.height() > 0);
+ }
- final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
- final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean parentVisible = false;
- boolean hasClipBounds = child.getClipBounds(mTmpRect);
- if (includeChildInDismissAll(row, selection)) {
- viewsToRemove.add(row);
- if (child.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- viewsToHide.add(child);
- parentVisible = true;
- }
- } else if (child.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- parentVisible = true;
- }
- List<ExpandableNotificationRow> children = row.getAttachedChildren();
- if (children != null) {
- for (ExpandableNotificationRow childRow : children) {
- if (includeChildInDismissAll(row, selection)) {
- viewsToRemove.add(childRow);
- if (parentVisible && row.areChildrenExpanded()) {
- hasClipBounds = childRow.getClipBounds(mTmpRect);
- if (childRow.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- viewsToHide.add(childRow);
- }
- }
+ private boolean shouldHideParent(View view, @SelectedRows int selection) {
+ final boolean silentSectionWillBeGone =
+ !mController.hasNotifications(ROWS_GENTLE, false /* clearable */);
+
+ // The only SectionHeaderView we have is the silent section header.
+ if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
+ return true;
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (isVisible(row) && includeChildInDismissAll(row, selection)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isChildrenVisible(ExpandableNotificationRow parent) {
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+ return isVisible(parent)
+ && children != null
+ && parent.areChildrenExpanded();
+ }
+
+ // Similar to #getRowsToDismissInBackend, but filters for visible views.
+ private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) {
+ final int viewCount = getChildCount();
+ final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);
+
+ for (int i = 0; i < viewCount; i++) {
+ final View view = getChildAt(i);
+
+ if (shouldHideParent(view, selection)) {
+ viewsToHide.add(view);
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+
+ if (isChildrenVisible(parent)) {
+ for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
+ if (isVisible(child) && includeChildInDismissAll(child, selection)) {
+ viewsToHide.add(child);
}
}
}
}
}
+ return viewsToHide;
+ }
+ private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend(
+ @SelectedRows int selection) {
+ final int childCount = getChildCount();
+ final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(childCount);
+
+ for (int i = 0; i < childCount; i++) {
+ final View view = getChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ if (includeChildInDismissAll(parent, selection)) {
+ viewsToRemove.add(parent);
+ }
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+ if (isVisible(parent) && children != null) {
+ for (ExpandableNotificationRow child : children) {
+ if (includeChildInDismissAll(parent, selection)) {
+ viewsToRemove.add(child);
+ }
+ }
+ }
+ }
+ return viewsToRemove;
+ }
+
+ /**
+ * Collects a list of visible rows, and animates them away in a staggered fashion as if they
+ * were dismissed. Notifications are dismissed in the backend via onDismissAllAnimationsEnd.
+ */
+ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
+ @VisibleForTesting
+ void clearNotifications(@SelectedRows int selection, boolean closeShade) {
+ // Animate-swipe all dismissable notifications, then animate the shade closed
+ final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
+ final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
+ getRowsToDismissInBackend(selection);
if (mDismissListener != null) {
mDismissListener.onDismiss(selection);
}
-
- if (viewsToRemove.isEmpty()) {
- if (closeShade && mShadeController != null) {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- }
+ final Runnable dismissInBackend = () -> {
+ onDismissAllAnimationsEnd(rowsToDismissInBackend, selection);
+ };
+ if (viewsToAnimateAway.isEmpty()) {
+ dismissInBackend.run();
return;
}
+ // Disable normal animations
+ setDismissAllInProgress(true);
+ mShadeNeedsToClose = closeShade;
- performDismissAllAnimations(
- viewsToHide,
- closeShade,
- () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
+ // Decrease the delay for every row we animate to give the sense of
+ // accelerating the swipes
+ final int rowDelayDecrement = 5;
+ int currentDelay = 60;
+ int totalDelay = 0;
+ final int numItems = viewsToAnimateAway.size();
+ for (int i = numItems - 1; i >= 0; i--) {
+ View view = viewsToAnimateAway.get(i);
+ Runnable endRunnable = null;
+ if (i == 0) {
+ endRunnable = dismissInBackend;
+ }
+ dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
+ currentDelay = Math.max(30, currentDelay - rowDelayDecrement);
+ totalDelay += currentDelay;
+ }
}
private boolean includeChildInDismissAll(
@@ -5013,63 +5103,6 @@
return canChildBeDismissed(row) && matchesSelection(row, selection);
}
- /**
- * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
- * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
- * handler.
- *
- * @param hideAnimatedList List of rows to animated away. Should only be views that are
- * currently visible, or else the stagger will look funky.
- * @param closeShade Whether to close the shade after the stagger animation completes.
- * @param onAnimationComplete Called after the entire animation completes (including the shade
- * closing if appropriate). The rows must be dismissed for real here.
- */
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- private void performDismissAllAnimations(
- final ArrayList<View> hideAnimatedList,
- final boolean closeShade,
- final Runnable onAnimationComplete) {
-
- final Runnable onSlideAwayAnimationComplete = () -> {
- if (closeShade) {
- mShadeController.addPostCollapseAction(() -> {
- setDismissAllInProgress(false);
- onAnimationComplete.run();
- });
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE);
- } else {
- setDismissAllInProgress(false);
- onAnimationComplete.run();
- }
- };
-
- if (hideAnimatedList.isEmpty()) {
- onSlideAwayAnimationComplete.run();
- return;
- }
-
- // let's disable our normal animations
- setDismissAllInProgress(true);
-
- // Decrease the delay for every row we animate to give the sense of
- // accelerating the swipes
- int rowDelayDecrement = 10;
- int currentDelay = 140;
- int totalDelay = 180;
- int numItems = hideAnimatedList.size();
- for (int i = numItems - 1; i >= 0; i--) {
- View view = hideAnimatedList.get(i);
- Runnable endRunnable = null;
- if (i == 0) {
- endRunnable = onSlideAwayAnimationComplete;
- }
- dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
- currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
- totalDelay += currentDelay;
- }
- }
-
public void setNotificationActivityStarter(
NotificationActivityStarter notificationActivityStarter) {
mNotificationActivityStarter = notificationActivityStarter;
@@ -5085,6 +5118,7 @@
mFooterDismissListener.onDismiss();
}
clearNotifications(ROWS_ALL, true /* closeShade */);
+ footerView.setSecondaryVisible(false /* visible */, true /* animate */);
});
footerView.setManageButtonClickListener(v -> {
mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown());
@@ -5567,6 +5601,16 @@
}
/**
+ * Sets whether the current user is set up, which is required to show the footer (b/193149550)
+ */
+ public void setCurrentUserSetup(boolean isCurrentUserSetup) {
+ if (mIsCurrentUserSetup != isCurrentUserSetup) {
+ mIsCurrentUserSetup = isCurrentUserSetup;
+ updateFooter();
+ }
+ }
+
+ /**
* A listener that is notified when the empty space below the notifications is clicked on
*/
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
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 9e4adce..1e92ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -117,6 +117,8 @@
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
@@ -144,6 +146,7 @@
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationRoundnessManager mNotificationRoundnessManager;
private final TunerService mTunerService;
+ private final DeviceProvisionedController mDeviceProvisionedController;
private final DynamicPrivacyController mDynamicPrivacyController;
private final ConfigurationController mConfigurationController;
private final ZenModeController mZenModeController;
@@ -171,6 +174,7 @@
private final NotificationLockscreenUserManager mLockscreenUserManager;
// TODO: StatusBar should be encapsulated behind a Controller
private final StatusBar mStatusBar;
+ private final NotificationGroupManagerLegacy mLegacyGroupManager;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@@ -218,6 +222,28 @@
}
};
+ private final DeviceProvisionedListener mDeviceProvisionedListener =
+ new DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ updateCurrentUserIsSetup();
+ }
+
+ @Override
+ public void onUserSwitched() {
+ updateCurrentUserIsSetup();
+ }
+
+ @Override
+ public void onUserSetupChanged() {
+ updateCurrentUserIsSetup();
+ }
+
+ private void updateCurrentUserIsSetup() {
+ mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup());
+ }
+ };
+
private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
if (mView.isExpanded()) {
// The bottom might change because we're using the final actual height of the view
@@ -587,6 +613,7 @@
HeadsUpManagerPhone headsUpManager,
NotificationRoundnessManager notificationRoundnessManager,
TunerService tunerService,
+ DeviceProvisionedController deviceProvisionedController,
DynamicPrivacyController dynamicPrivacyController,
ConfigurationController configurationController,
SysuiStatusBarStateController statusBarStateController,
@@ -623,6 +650,7 @@
mHeadsUpManager = headsUpManager;
mNotificationRoundnessManager = notificationRoundnessManager;
mTunerService = tunerService;
+ mDeviceProvisionedController = deviceProvisionedController;
mDynamicPrivacyController = dynamicPrivacyController;
mConfigurationController = configurationController;
mStatusBarStateController = statusBarStateController;
@@ -651,6 +679,8 @@
mStatusBar.requestNotificationUpdate("onGroupsChanged");
}
});
+ mLegacyGroupManager = featureFlags.isNewNotifPipelineRenderingEnabled()
+ ? null : legacyGroupManager;
mSilentHeaderController = silentHeaderController;
mFeatureFlags = featureFlags;
mNotifPipeline = notifPipeline;
@@ -759,6 +789,9 @@
return Unit.INSTANCE;
});
+ // callback is invoked synchronously, updating mView immediately
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
if (mView.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
@@ -1122,6 +1155,10 @@
mZenModeController.areNotificationsHiddenInShade());
}
+ public boolean areNotificationsHiddenInShade() {
+ return mZenModeController.areNotificationsHiddenInShade();
+ }
+
public boolean isShowingEmptyShadeView() {
return mShowEmptyShadeView;
}
@@ -1170,6 +1207,10 @@
* Return whether there are any clearable notifications
*/
public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
+ return hasNotifications(selection, true /* clearable */);
+ }
+
+ public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
if (mDynamicPrivacyController.isInLockedDownShade()) {
return false;
}
@@ -1180,9 +1221,16 @@
continue;
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (row.canViewBeDismissed() &&
- NotificationStackScrollLayout.matchesSelection(row, selection)) {
- return true;
+ final boolean matchClearable =
+ isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed();
+ final boolean inSection =
+ NotificationStackScrollLayout.matchesSelection(row, selection);
+ if (matchClearable && inSection) {
+ if (mLegacyGroupManager == null
+ || !mLegacyGroupManager.isSummaryOfSuppressedGroup(
+ row.getEntry().getSbn())) {
+ return true;
+ }
}
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 2c810c9..8be5de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -366,6 +366,20 @@
return stackHeight / stackEndHeight;
}
+ public boolean hasOngoingNotifs(StackScrollAlgorithmState algorithmState) {
+ for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
+ View child = algorithmState.visibleChildren.get(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (!row.canViewBeDismissed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
@@ -430,7 +444,9 @@
+ view.getIntrinsicHeight();
final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
((FooterView.FooterViewState) viewState).hideContent =
- isShelfShowing || noSpaceForFooter;
+ isShelfShowing || noSpaceForFooter
+ || (ambientState.isDismissAllInProgress()
+ && !hasOngoingNotifs(algorithmState));
}
} else {
if (view != ambientState.getTrackedHeadsUpRow()) {
@@ -467,7 +483,6 @@
}
}
}
-
// Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index ee12b4b..4466cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -46,7 +46,7 @@
public static final int ANIMATION_DURATION_WAKEUP = 500;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
- public static final int ANIMATION_DURATION_SWIPE = 260;
+ public static final int ANIMATION_DURATION_SWIPE = 200;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6e201048..2c76cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -116,7 +116,7 @@
/**
* Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
- * device while being requested when keyguard is occluded.
+ * device while being requested when keyguard is occluded or showing.
*/
public static final int MODE_UNLOCK_COLLAPSING = 5;
@@ -425,6 +425,11 @@
if (!wasDeviceInteractive) {
mPendingShowBouncer = true;
} else {
+ mShadeController.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_NONE,
+ true /* force */,
+ false /* delayed */,
+ BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
mKeyguardViewController.notifyKeyguardAuthenticated(
false /* strongAuth */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 5a6db21..e67c6ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -291,6 +291,7 @@
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.print("getAlwaysOn(): "); pw.println(getAlwaysOn());
pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
pw.print("getPulseDuration(): "); pw.println(getPulseDuration());
pw.print("getPulseInDuration(): "); pw.println(getPulseInDuration());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index b2cf72a..21c3e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -116,11 +116,18 @@
if (!mDozing || mPulseCallback != null) {
if (DEBUG) {
- Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+ Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? "
+ (mPulseCallback != null));
}
// Pulse suppressed.
callback.onPulseFinished();
+ if (!mDozing) {
+ mDozeLog.tracePulseDropped("device isn't dozing");
+ } else {
+ mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback="
+ + mPulseCallback);
+ }
+
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 0a4e59c..4701d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -203,13 +203,15 @@
private ControlsListingController.ControlsListingCallback mListingCallback =
new ControlsListingController.ControlsListingCallback() {
public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
- boolean available = !serviceInfos.isEmpty();
+ post(() -> {
+ boolean available = !serviceInfos.isEmpty();
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateControlsVisibility();
- updateAffordanceColors();
- }
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateControlsVisibility();
+ updateAffordanceColors();
+ }
+ });
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index f77c052..b58cab4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -83,8 +83,7 @@
private int mNotificationStackHeight;
/**
- * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
- * avatar.
+ * Minimum top margin to avoid overlap with status bar, or multi-user switcher avatar.
*/
private int mMinTopMargin;
@@ -150,6 +149,25 @@
private boolean mIsSplitShade;
/**
+ * Top location of the udfps icon. This includes the worst case (highest) burn-in
+ * offset that would make the top physically highest on the screen.
+ *
+ * Set to -1 if udfps is not enrolled on the device.
+ */
+ private float mUdfpsTop;
+
+ /**
+ * Bottom y-position of the currently visible clock
+ */
+ private float mClockBottom;
+
+ /**
+ * If true, try to keep clock aligned to the top of the display. Else, assume the clock
+ * is center aligned.
+ */
+ private boolean mIsClockTopAligned;
+
+ /**
* Refreshes the dimension values.
*/
public void loadDimens(Resources res) {
@@ -157,7 +175,7 @@
R.dimen.keyguard_status_view_bottom_margin);
mContainerTopPadding =
- res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) / 2;
+ res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
mBurnInPreventionOffsetX = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetY = res.getDimensionPixelSize(
@@ -174,7 +192,8 @@
int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
boolean hasCustomClock, boolean hasVisibleNotifs, float dark,
float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding,
- float qsExpansion, int cutoutTopInset, boolean isSplitShade) {
+ float qsExpansion, int cutoutTopInset, boolean isSplitShade, float udfpsTop,
+ float clockBottom, boolean isClockTopAligned) {
mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
userSwitchHeight);
mMaxShadeBottom = maxShadeBottom;
@@ -193,6 +212,9 @@
mQsExpansion = qsExpansion;
mCutoutTopInset = cutoutTopInset;
mIsSplitShade = isSplitShade;
+ mUdfpsTop = udfpsTop;
+ mClockBottom = clockBottom;
+ mIsClockTopAligned = isClockTopAligned;
}
public void run(Result result) {
@@ -247,8 +269,34 @@
if (clockY - mBurnInPreventionOffsetYLargeClock < mCutoutTopInset) {
shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYLargeClock);
}
- float clockYDark = clockY + burnInPreventionOffsetY() + shift;
+ int burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock; // requested offset
+ final boolean hasUdfps = mUdfpsTop > -1;
+ if (hasUdfps && !mIsClockTopAligned) {
+ // ensure clock doesn't overlap with the udfps icon
+ if (mUdfpsTop < mClockBottom) {
+ // sometimes the clock textView extends beyond udfps, so let's just use the
+ // space above the KeyguardStatusView/clock as our burn-in offset
+ burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
+ if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+ }
+ shift = -burnInPreventionOffsetY;
+ } else {
+ float upperSpace = clockY - mCutoutTopInset;
+ float lowerSpace = mUdfpsTop - mClockBottom;
+ // center the burn-in offset within the upper + lower space
+ burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
+ if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+ }
+ shift = (lowerSpace - upperSpace) / 2;
+ }
+ }
+
+ float clockYDark = clockY
+ + burnInPreventionOffsetY(burnInPreventionOffsetY)
+ + shift;
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
}
@@ -280,9 +328,7 @@
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
}
- private float burnInPreventionOffsetY() {
- int offset = mBurnInPreventionOffsetYLargeClock;
-
+ private float burnInPreventionOffsetY(int offset) {
return getBurnInOffset(offset * 2, false /* xAxis */) - offset;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index f1cde8a..a5b5f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -28,7 +28,10 @@
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.StyleRes;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.keyguard.KeyguardIndication;
@@ -39,6 +42,12 @@
*/
public class KeyguardIndicationTextView extends TextView {
private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500;
+
+ @StyleRes
+ private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
+ @StyleRes
+ private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button;
+
private long mNextAnimationTime = 0;
private boolean mAnimationsEnabled = true;
private LinkedList<CharSequence> mMessages = new LinkedList<>();
@@ -136,6 +145,14 @@
public void onAnimationEnd(Animator animator) {
KeyguardIndication info = mKeyguardIndicationInfo.poll();
if (info != null) {
+ // First, update the style.
+ // If a background is set on the text, we don't want shadow on the text
+ if (info.getBackground() != null) {
+ setTextAppearance(sButtonStyleId);
+ } else {
+ setTextAppearance(sStyleId);
+ }
+ setBackground(info.getBackground());
setTextColor(info.getTextColor());
setOnClickListener(info.getClickListener());
setClickable(info.getClickListener() != null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e272d27..55e0c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -96,7 +96,8 @@
private final UserManager mUserManager;
private int mSystemIconsSwitcherHiddenExpandedMargin;
- private int mSystemIconsBaseMargin;
+ private int mStatusBarPaddingEnd;
+ private int mMinDotWidth;
private View mSystemIconsContainer;
private TintedIconManager mIconManager;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -157,14 +158,7 @@
mMultiUserAvatar.setLayoutParams(lp);
// System icons
- lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
- lp.setMarginStart(getResources().getDimensionPixelSize(
- R.dimen.system_icons_super_container_margin_start));
- mSystemIconsContainer.setLayoutParams(lp);
- mSystemIconsContainer.setPaddingRelative(mSystemIconsContainer.getPaddingStart(),
- mSystemIconsContainer.getPaddingTop(),
- getResources().getDimensionPixelSize(R.dimen.system_icons_keyguard_padding_end),
- mSystemIconsContainer.getPaddingBottom());
+ updateSystemIconsLayoutParams();
// Respect font size setting.
mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
@@ -194,8 +188,10 @@
Resources res = getResources();
mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
R.dimen.system_icons_switcher_hidden_expanded_margin);
- mSystemIconsBaseMargin = res.getDimensionPixelSize(
- R.dimen.system_icons_super_container_avatarless_margin_end);
+ mStatusBarPaddingEnd = res.getDimensionPixelSize(
+ R.dimen.status_bar_padding_end);
+ mMinDotWidth = res.getDimensionPixelSize(
+ R.dimen.ongoing_appops_dot_min_padding);
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
mShowPercentAvailable = getContext().getResources().getBoolean(
@@ -243,16 +239,24 @@
private void updateSystemIconsLayoutParams() {
LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- // If the avatar icon is gone, we need to have some end margin to display the system icons
- // correctly.
- int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
- ? mSystemIconsBaseMargin
- : 0;
+
+ int marginStart = getResources().getDimensionPixelSize(
+ R.dimen.system_icons_super_container_margin_start);
+
+ // Use status_bar_padding_end to replace original
+ // system_icons_super_container_avatarless_margin_end to prevent different end alignment
+ // between PhoneStatusBarView and KeyguardStatusBarView
+ int baseMarginEnd = mStatusBarPaddingEnd;
int marginEnd =
mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
: baseMarginEnd;
- marginEnd = calculateMargin(marginEnd, mPadding.second);
- if (marginEnd != lp.getMarginEnd()) {
+
+ // Align PhoneStatusBar right margin/padding, only use
+ // 1. status bar layout: mPadding(consider round_corner + privacy dot)
+ // 2. icon container: R.dimen.status_bar_padding_end
+
+ if (marginEnd != lp.getMarginEnd() || marginStart != lp.getMarginStart()) {
+ lp.setMarginStart(marginStart);
lp.setMarginEnd(marginEnd);
mSystemIconsContainer.setLayoutParams(lp);
}
@@ -287,7 +291,13 @@
mPadding =
StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
- setPadding(mPadding.first, waterfallTop, mPadding.second, 0);
+
+ // consider privacy dot space
+ final int minLeft = isLayoutRtl() ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first;
+ final int minRight = isLayoutRtl() ? mPadding.second :
+ Math.max(mMinDotWidth, mPadding.second);
+
+ setPadding(minLeft, waterfallTop, minRight, 0);
}
private boolean updateLayoutParamsNoCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 15e0716..58cbe83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -51,6 +51,7 @@
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
@@ -621,6 +622,8 @@
*/
private float mKeyguardOnlyContentAlpha = 1.0f;
+ private float mUdfpsMaxYBurnInOffset;
+
/**
* Are we currently in gesture navigation
*/
@@ -635,6 +638,7 @@
private int mQsClipBottom;
private boolean mQsVisible;
private final ContentResolver mContentResolver;
+ private float mMinFraction;
private final Executor mUiExecutor;
private final SecureSettings mSecureSettings;
@@ -852,13 +856,13 @@
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
- UserAvatarView userAvatarView = null;
+ FrameLayout userAvatarContainer = null;
KeyguardUserSwitcherView keyguardUserSwitcherView = null;
if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
if (mKeyguardQsUserSwitchEnabled) {
ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
- userAvatarView = (UserAvatarView) stub.inflate();
+ userAvatarContainer = (FrameLayout) stub.inflate();
} else {
ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
@@ -867,7 +871,7 @@
updateViewControllers(
mView.findViewById(R.id.keyguard_status_view),
- userAvatarView,
+ userAvatarContainer,
mKeyguardStatusBar,
keyguardUserSwitcherView);
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
@@ -956,10 +960,11 @@
mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources);
mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
+ mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
- UserAvatarView userAvatarView,
+ FrameLayout userAvatarView,
KeyguardStatusBarView keyguardStatusBarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
@@ -1111,7 +1116,7 @@
!mKeyguardQsUserSwitchEnabled
&& mKeyguardUserSwitcherEnabled
&& isUserSwitcherEnabled;
- UserAvatarView userAvatarView = (UserAvatarView) reInflateStub(
+ FrameLayout userAvatarView = (FrameLayout) reInflateStub(
R.id.keyguard_qs_user_switch_view /* viewId */,
R.id.keyguard_qs_user_switch_stub /* stubId */,
R.layout.keyguard_qs_user_switch /* layoutId */,
@@ -1300,7 +1305,16 @@
float darkamount =
mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
? 1.0f : mInterpolatedDarkAmount;
- mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard,
+
+ float udfpsAodTopLocation = -1f;
+ if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) {
+ FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
+ udfpsAodTopLocation = props.sensorLocationY - props.sensorRadius
+ - mUdfpsMaxYBurnInOffset;
+ }
+
+ mClockPositionAlgorithm.setup(
+ mStatusBarHeaderHeightKeyguard,
totalHeight - bottomPadding,
mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
expandedFraction,
@@ -1312,7 +1326,10 @@
bypassEnabled, getUnlockedStackScrollerPadding(),
computeQsExpansionFraction(),
mDisplayTopInset,
- mShouldUseSplitNotificationShade);
+ mShouldUseSplitNotificationShade,
+ udfpsAodTopLocation,
+ mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
+ mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = animate || mAnimateNextPositionUpdate;
@@ -1795,6 +1812,15 @@
return !mQsTouchAboveFalsingThreshold;
}
+ /**
+ * Percentage of panel expansion offset, caused by pulling down on a heads-up.
+ */
+ @Override
+ public void setMinFraction(float minFraction) {
+ mMinFraction = minFraction;
+ mDepthController.setPanelPullDownMinFraction(mMinFraction);
+ }
+
private float computeQsExpansionFraction() {
if (mQSAnimatingHiddenFromCollapsed) {
// When hiding QS from collapsed state, the expansion can sometimes temporarily
@@ -2309,6 +2335,12 @@
}
}
top += mOverStretchAmount;
+ // Correction for instant expansion caused by HUN pull down/
+ if (mMinFraction > 0f && mMinFraction < 1f) {
+ float realFraction =
+ (getExpandedFraction() - mMinFraction) / (1f - mMinFraction);
+ top *= MathUtils.saturate(realFraction / mMinFraction);
+ }
bottom = getView().getBottom();
// notification bounds should take full screen width regardless of insets
left = 0;
@@ -3439,7 +3471,7 @@
}
public void setPanelScrimMinFraction(float minFraction) {
- mBar.panelScrimMinFractionChanged(minFraction);
+ mBar.onPanelMinFractionChanged(minFraction);
}
public void clearNotificationEffects() {
@@ -3655,6 +3687,7 @@
}
public void dozeTimeTick() {
+ mLockIconViewController.dozeTimeTick();
mKeyguardBottomArea.dozeTimeTick();
mKeyguardStatusViewController.dozeTimeTick();
if (mInterpolatedDarkAmount > 0) {
@@ -3868,6 +3901,9 @@
@Override
protected TouchHandler createTouchHandler() {
return new TouchHandler() {
+
+ private long mLastTouchDownTime = -1L;
+
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
@@ -3897,6 +3933,19 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (event.getDownTime() == mLastTouchDownTime) {
+ // An issue can occur when swiping down after unlock, where multiple down
+ // events are received in this handler with identical downTimes. Until the
+ // source of the issue can be located, detect this case and ignore.
+ // see b/193350347
+ Log.w(TAG, "Duplicate down event detected... ignoring");
+ return true;
+ }
+ mLastTouchDownTime = event.getDownTime();
+ }
+
+
if (mBlockTouches || (mQsFullyExpanded && mQs != null
&& mQs.disallowPanelTouches())) {
return false;
@@ -3958,10 +4007,6 @@
mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
}
- if (mLockIconViewController.onTouchEvent(event)) {
- return true;
- }
-
handled |= super.onTouch(v, event);
return !mDozing || mPulsing || handled;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 246810a..c26782b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -605,12 +605,11 @@
}
@Override
- public void setLightRevealScrimAmount(float amount) {
- boolean lightRevealScrimOpaque = amount == 0;
- if (mCurrentState.mLightRevealScrimOpaque == lightRevealScrimOpaque) {
+ public void setLightRevealScrimOpaque(boolean opaque) {
+ if (mCurrentState.mLightRevealScrimOpaque == opaque) {
return;
}
- mCurrentState.mLightRevealScrimOpaque = lightRevealScrimOpaque;
+ mCurrentState.mLightRevealScrimOpaque = opaque;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index b5d9bd6..3807b46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -35,6 +35,7 @@
import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.LockIconViewController;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dock.DockManager;
@@ -88,6 +89,7 @@
private final NotificationShadeDepthController mDepthController;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private final LockIconViewController mLockIconViewController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private GestureDetector mGestureDetector;
@@ -138,7 +140,8 @@
NotificationPanelViewController notificationPanelViewController,
SuperStatusBarViewFactory statusBarViewFactory,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ LockIconViewController lockIconViewController) {
mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
@@ -163,6 +166,7 @@
mStatusBarViewFactory = statusBarViewFactory;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLockIconViewController = lockIconViewController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -235,6 +239,7 @@
if (!isCancel && mService.shouldIgnoreTouch()) {
return false;
}
+
if (isDown) {
setTouchActive(true);
mTouchCancelled = false;
@@ -245,6 +250,7 @@
if (mTouchCancelled || mExpandAnimationRunning) {
return false;
}
+
mFalsingCollector.onTouchEvent(ev);
mGestureDetector.onTouchEvent(ev);
mStatusBarKeyguardViewManager.onTouch(ev);
@@ -260,9 +266,17 @@
if (isDown) {
mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev);
}
+
if (mStatusBarStateController.isDozing()) {
mService.mDozeScrimController.extendPulse();
}
+ mLockIconViewController.onTouchEvent(
+ ev,
+ () -> mService.wakeUpIfDozing(
+ SystemClock.uptimeMillis(),
+ mView,
+ "LOCK_ICON_TOUCH"));
+
// In case we start outside of the view bounds (below the status bar), we need to
// dispatch
// the touch manually as the view system can't accommodate for touches outside of
@@ -311,6 +325,12 @@
// Capture all touch events in always-on.
return true;
}
+
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ // capture all touches if the alt auth bouncer is showing
+ return true;
+ }
+
boolean intercept = false;
if (mNotificationPanelViewController.isFullyExpanded()
&& mDragDownHelper.isDragDownEnabled()
@@ -338,6 +358,12 @@
if (mStatusBarStateController.isDozing()) {
handled = !mService.isPulsing();
}
+
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ // eat the touch
+ handled = true;
+ }
+
if ((mDragDownHelper.isDragDownEnabled() && !handled)
|| mDragDownHelper.isDraggingDown()) {
// we still want to finish our drag down gesture when locking the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index f1b6c7c..eca91a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -18,6 +18,7 @@
import static java.lang.Float.isNaN;
+import android.annotation.CallSuper;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
@@ -162,7 +163,13 @@
return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
}
- public abstract void panelScrimMinFractionChanged(float minFraction);
+ /**
+ * Percentage of panel expansion offset, caused by pulling down on a heads-up.
+ */
+ @CallSuper
+ public void onPanelMinFractionChanged(float minFraction) {
+ mPanel.setMinFraction(minFraction);
+ }
/**
* @param frac the fraction from the expansion in [0, 1]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 323a112..99c0c13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -338,6 +338,13 @@
protected abstract float getOpeningHeight();
/**
+ * Minimum fraction from where expansion should start. This is set when pulling down on a
+ * heads-up notification.
+ * @param minFraction Fraction from 0 to 1.
+ */
+ public abstract void setMinFraction(float minFraction);
+
+ /**
* @return whether the swiping direction is upwards and above a 45 degree angle compared to the
* horizontal direction
*/
@@ -1211,10 +1218,14 @@
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
addMovement(event);
- if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+ final boolean openShadeWithoutHun =
+ mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
+ if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
+ || openShadeWithoutHun) {
float hAbs = Math.abs(h);
float touchSlop = getTouchSlop(event);
- if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop))
+ if ((h < -touchSlop
+ || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
&& hAbs > Math.abs(x - mInitialTouchX)) {
cancelHeightAnimator();
startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
@@ -1227,10 +1238,7 @@
mVelocityTracker.clear();
break;
}
-
- // Finally, if none of the above cases applies, ensure that touches do not get handled
- // by the contents of a panel that is not showing (a bit of a hack to avoid b/178277858)
- return (mView.getVisibility() != View.VISIBLE);
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c300b11..2ca36b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -269,10 +269,11 @@
}
@Override
- public void panelScrimMinFractionChanged(float minFraction) {
+ public void onPanelMinFractionChanged(float minFraction) {
if (isNaN(minFraction)) {
throw new IllegalArgumentException("minFraction cannot be NaN");
}
+ super.onPanelMinFractionChanged(minFraction);
if (mMinFraction != minFraction) {
mMinFraction = minFraction;
updateScrimFraction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index cfcea96..27c129a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -111,6 +111,20 @@
*/
private boolean mTransitioningToFullShade;
+ /**
+ * Is there currently an unocclusion animation running. Used to avoid bright flickers
+ * of the notification scrim.
+ */
+ private boolean mUnOcclusionAnimationRunning;
+
+ /**
+ * Set whether an unocclusion animation is currently running on the notification panel. Used
+ * to avoid bright flickers of the notification scrim.
+ */
+ public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) {
+ mUnOcclusionAnimationRunning = unocclusionAnimationRunning;
+ }
+
@IntDef(prefix = {"VISIBILITY_"}, value = {
TRANSPARENT,
SEMI_TRANSPARENT,
@@ -418,7 +432,7 @@
if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
scheduleUpdate();
- } else if ((oldState == ScrimState.AOD // leaving doze
+ } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING) // leaving doze
&& (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED))
|| (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) {
// Scheduling a frame isn't enough when:
@@ -466,6 +480,7 @@
public void onExpandingFinished() {
mTracking = false;
+ setUnocclusionAnimationRunning(false);
}
@VisibleForTesting
@@ -572,6 +587,8 @@
if (isNaN(expansionFraction)) {
return;
}
+ expansionFraction = Interpolators
+ .getNotificationScrimAlpha(expansionFraction, false /* notification */);
boolean qsBottomVisible = qsPanelBottomY > 0;
if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
mQsExpansion = expansionFraction;
@@ -694,6 +711,11 @@
mNotificationsTint = mState.getNotifTint();
mBehindTint = behindTint;
}
+ if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) {
+ // We're unoccluding the keyguard and don't want to have a bright flash.
+ mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
+ mNotificationsTint = ScrimState.KEYGUARD.getNotifTint();
+ }
}
if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
@@ -1242,6 +1264,8 @@
pw.println(mDefaultScrimAlpha);
pw.print(" mExpansionFraction=");
pw.println(mPanelExpansion);
+ pw.print(" mExpansionAffectsAlpha=");
+ pw.println(mExpansionAffectsAlpha);
pw.print(" mState.getMaxLightRevealScrimAlpha=");
pw.println(mState.getMaxLightRevealScrimAlpha());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 0681193..15b8c67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -244,7 +244,8 @@
? mKeyguardFadingAwayDuration
: StatusBar.FADE_KEYGUARD_DURATION;
- mAnimateChange = !mLaunchingAffordanceWithPreview;
+ boolean fromAod = previousState == AOD || previousState == PULSING;
+ mAnimateChange = !mLaunchingAffordanceWithPreview && !fromAod;
mFrontTint = Color.TRANSPARENT;
mBehindTint = Color.BLACK;
@@ -278,15 +279,41 @@
BUBBLE_EXPANDED {
@Override
public void prepare(ScrimState previousState) {
- mFrontTint = Color.TRANSPARENT;
- mBehindTint = Color.TRANSPARENT;
- mBubbleTint = Color.BLACK;
+ mBehindAlpha = mClipQsScrim ? 1 : 0;
+ mNotifAlpha = 0;
+ mFrontAlpha = 0;
- mFrontAlpha = 0f;
- mBehindAlpha = mDefaultScrimAlpha;
+ mAnimationDuration = mKeyguardFadingAway
+ ? mKeyguardFadingAwayDuration
+ : StatusBar.FADE_KEYGUARD_DURATION;
+
+ mAnimateChange = !mLaunchingAffordanceWithPreview;
+
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.BLACK;
+ mBlankScreen = false;
+
+ if (previousState == ScrimState.AOD) {
+ // Set all scrims black, before they fade transparent.
+ updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+ if (mScrimForBubble != null) {
+ updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+ }
+
+ // Scrims should still be black at the end of the transition.
+ mFrontTint = Color.BLACK;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.BLACK;
+ mBlankScreen = true;
+ }
+
+ if (mClipQsScrim) {
+ updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ }
mAnimationDuration = ScrimController.ANIMATION_DURATION;
- mBlankScreen = false;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 394e4ad..88434cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -592,6 +592,7 @@
mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+ mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
}
};
@@ -1044,7 +1045,7 @@
mNotificationShadeWindowViewController,
mNotificationPanelViewController,
mAmbientIndicationContainer);
- mDozeParameters.addCallback(this::updateLightRevealScrimVisibility);
+ updateLightRevealScrimVisibility();
mConfigurationController.addCallback(this);
@@ -1148,6 +1149,9 @@
mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setScrimController(mScrimController);
mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
+ for (ExpansionChangedListener listener : mExpansionChangedListeners) {
+ sendInitialExpansionAmount(listener);
+ }
// CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
// mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false.
@@ -1258,8 +1262,19 @@
mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble);
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
- mLightRevealScrim.setRevealAmountListener(
- mNotificationShadeWindowController::setLightRevealScrimAmount);
+ mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
+ Runnable updateOpaqueness = () -> {
+ mNotificationShadeWindowController.setLightRevealScrimOpaque(
+ mLightRevealScrim.isScrimOpaque());
+ };
+ if (opaque) {
+ // Delay making the view opaque for a frame, because it needs some time to render
+ // otherwise this can lead to a flicker where the scrim doesn't cover the screen
+ mLightRevealScrim.post(updateOpaqueness);
+ } else {
+ updateOpaqueness.run();
+ }
+ });
mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim);
updateLightRevealScrimVisibility();
@@ -3579,6 +3594,7 @@
public void animateKeyguardUnoccluding() {
mNotificationPanelViewController.setExpandedFraction(0f);
animateExpandNotificationsPanel();
+ mScrimController.setUnocclusionAnimationRunning(true);
}
/**
@@ -3909,7 +3925,8 @@
@Override
public void onDozeAmountChanged(float linear, float eased) {
if (mFeatureFlags.useNewLockscreenAnimations()
- && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
+ && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
+ && !mBiometricUnlockController.isWakeAndUnlock()) {
mLightRevealScrim.setRevealAmount(1f - linear);
}
}
@@ -4449,8 +4466,11 @@
|| mKeyguardStateController.isKeyguardFadingAway();
// Do not animate the scrim expansion when triggered by the fingerprint sensor.
- mScrimController.setExpansionAffectsAlpha(
- !mBiometricUnlockController.isBiometricUnlock());
+ boolean onKeyguardOrHidingIt = mKeyguardStateController.isShowing()
+ || mKeyguardStateController.isKeyguardFadingAway()
+ || mKeyguardStateController.isKeyguardGoingAway();
+ mScrimController.setExpansionAffectsAlpha(!(mBiometricUnlockController.isBiometricUnlock()
+ && onKeyguardOrHidingIt));
boolean launchingAffordanceWithPreview =
mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
@@ -4465,10 +4485,8 @@
ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming()
? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
mScrimController.transitionTo(state);
- } else if (isInLaunchTransition()
- || mLaunchCameraWhenFinishedWaking
- || launchingAffordanceWithPreview) {
- // TODO(b/170133395) Investigate whether Emergency Gesture flag should be included here.
+ } else if (launchingAffordanceWithPreview) {
+ // We want to avoid animating when launching with a preview.
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
} else if (mBrightnessMirrorVisible) {
mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
@@ -4933,6 +4951,14 @@
public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
mExpansionChangedListeners.add(listener);
+ sendInitialExpansionAmount(listener);
+ }
+
+ private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) {
+ if (mStatusBarView != null) {
+ expansionChangedListener.onExpansionChanged(mStatusBarView.getExpansionFraction(),
+ mStatusBarView.isExpanded());
+ }
}
public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
@@ -4946,11 +4972,5 @@
}
mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
- if (mFeatureFlags.useNewLockscreenAnimations()
- && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) {
- mLightRevealScrim.setVisibility(View.VISIBLE);
- } else {
- mLightRevealScrim.setVisibility(View.GONE);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8a7708a..1e3e47b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -53,6 +53,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.QuickStepContract;
@@ -92,7 +93,8 @@
// with the appear animations of the PIN/pattern/password views.
private static final long NAV_BAR_SHOW_DELAY_BOUNCER = 320;
- private static final long WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS = 200;
+ // The duration to fade the nav bar content in/out when the device starts to sleep
+ private static final long NAV_BAR_CONTENT_FADE_DURATION = 125;
// Duration of the Keyguard dismissal animation in case the user is currently locked. This is to
// make everything a bit slower to bridge a gap until the user is unlocked and home screen has
@@ -196,7 +198,6 @@
private boolean mLastPulsing;
private int mLastBiometricMode;
private boolean mQsExpanded;
- private boolean mAnimatedToSleep;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -303,8 +304,10 @@
* Sets a new alt auth interceptor.
*/
public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
- mAlternateAuthInterceptor = authInterceptor;
- resetAlternateAuth(false);
+ if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
+ mAlternateAuthInterceptor = authInterceptor;
+ resetAlternateAuth(false);
+ }
}
private void registerListeners() {
@@ -317,20 +320,6 @@
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
- mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
- @Override
- public void onFinishedWakingUp() {
- mAnimatedToSleep = false;
- updateStates();
- }
-
- @Override
- public void onFinishedGoingToSleep() {
- mAnimatedToSleep =
- mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
- updateStates();
- }
- });
}
@Override
@@ -563,12 +552,26 @@
public void onStartedWakingUp() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.setAnimationsDisabled(false);
+ View currentView = getCurrentNavBarView();
+ if (currentView != null) {
+ currentView.animate()
+ .alpha(1f)
+ .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+ .start();
+ }
}
@Override
public void onStartedGoingToSleep() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.setAnimationsDisabled(true);
+ View currentView = getCurrentNavBarView();
+ if (currentView != null) {
+ currentView.animate()
+ .alpha(0f)
+ .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+ .start();
+ }
}
@Override
@@ -993,6 +996,25 @@
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
+ /**
+ * Updates the visibility of the nav bar content views.
+ */
+ private void updateNavigationBarContentVisibility(boolean navBarContentVisible) {
+ final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+ if (navBarView != null && navBarView.getCurrentView() != null) {
+ final View currentView = navBarView.getCurrentView();
+ currentView.setVisibility(navBarContentVisible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private View getCurrentNavBarView() {
+ final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+ return navBarView != null ? navBarView.getCurrentView() : null;
+ }
+
+ /**
+ * Updates the visibility of the nav bar window (which will cause insets changes).
+ */
protected void updateNavigationBarVisibility(boolean navBarVisible) {
if (mStatusBar.getNavigationBarView() != null) {
if (navBarVisible) {
@@ -1020,7 +1042,7 @@
boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked)
&& mGesturalNav;
- return (!mAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
+ return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
|| mRemoteInputActive || keyguardWithGestureNav
|| mGlobalActionsVisible);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 47deb1f..8821de0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -310,17 +310,11 @@
private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
- if (old != null) {
- if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
- && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
- if (mStatusBarStateController.getState() == StatusBarState.SHADE
- && reason != NotificationListenerService.REASON_CLICK) {
- mCommandQueue.animateCollapsePanels();
- } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+ if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
+ && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
+ && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
&& !isCollapsing()) {
- mStatusBarStateController.setState(StatusBarState.KEYGUARD);
- }
- }
+ mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 6b52dca..143aaba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -5,7 +5,9 @@
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
+import android.database.ContentObserver
import android.os.Handler
+import android.provider.Settings
import android.view.View
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
@@ -19,6 +21,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
/**
@@ -46,15 +49,19 @@
private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
private val keyguardStateController: KeyguardStateController,
- private val dozeParameters: dagger.Lazy<DozeParameters>
+ private val dozeParameters: dagger.Lazy<DozeParameters>,
+ private val globalSettings: GlobalSettings
) : WakefulnessLifecycle.Observer {
private val handler = Handler()
private lateinit var statusBar: StatusBar
private lateinit var lightRevealScrim: LightRevealScrim
+ private var animatorDurationScale = 1f
+ private var shouldAnimateInKeyguard = false
private var lightRevealAnimationPlaying = false
private var aodUiAnimationPlaying = false
+ private var callbacks = HashSet<Callback>()
/**
* The result of our decision whether to play the screen off animation in
@@ -66,11 +73,17 @@
private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
duration = LIGHT_REVEAL_ANIMATION_DURATION
interpolator = Interpolators.LINEAR
- addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float }
+ addUpdateListener {
+ lightRevealScrim.revealAmount = it.animatedValue as Float
+ sendUnlockedScreenOffProgressUpdate(
+ 1f - (it.animatedFraction as Float),
+ 1f - (it.animatedValue as Float))
+ }
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator?) {
lightRevealScrim.revealAmount = 1f
lightRevealAnimationPlaying = false
+ sendUnlockedScreenOffProgressUpdate(0f, 0f)
}
override fun onAnimationEnd(animation: Animator?) {
@@ -79,6 +92,12 @@
})
}
+ val animatorDurationScaleObserver = object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ updateAnimatorDurationScale()
+ }
+ }
+
fun initialize(
statusBar: StatusBar,
lightRevealScrim: LightRevealScrim
@@ -86,14 +105,25 @@
this.lightRevealScrim = lightRevealScrim
this.statusBar = statusBar
+ updateAnimatorDurationScale()
+ globalSettings.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+ /* notify for descendants */ false,
+ animatorDurationScaleObserver)
wakefulnessLifecycle.addObserver(this)
}
+ fun updateAnimatorDurationScale() {
+ animatorDurationScale =
+ globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+ }
+
/**
* Animates in the provided keyguard view, ending in the same position that it will be in on
* AOD.
*/
fun animateInKeyguard(keyguardView: View, after: Runnable) {
+ shouldAnimateInKeyguard = false
keyguardView.alpha = 0f
keyguardView.visibility = View.VISIBLE
@@ -138,6 +168,7 @@
// Waking up, so reset this flag.
decidedToAnimateGoingToSleep = null
+ shouldAnimateInKeyguard = false
lightRevealAnimator.cancel()
handler.removeCallbacksAndMessages(null)
}
@@ -146,7 +177,6 @@
// Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other
// observers (such as StatusBar) can ask us whether we were playing the screen off animation
// and reset accordingly.
- lightRevealAnimationPlaying = false
aodUiAnimationPlaying = false
// If we can't control the screen off animation, we shouldn't mess with the StatusBar's
@@ -167,15 +197,15 @@
if (dozeParameters.get().shouldControlUnlockedScreenOff()) {
decidedToAnimateGoingToSleep = true
+ shouldAnimateInKeyguard = true
lightRevealAnimationPlaying = true
lightRevealAnimator.start()
-
handler.postDelayed({
aodUiAnimationPlaying = true
// Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
statusBar.notificationPanelViewController.showAodUi()
- }, ANIMATE_IN_KEYGUARD_DELAY)
+ }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
} else {
decidedToAnimateGoingToSleep = false
}
@@ -220,7 +250,21 @@
return true
}
- /**
+ fun addCallback(callback: Callback) {
+ callbacks.add(callback)
+ }
+
+ fun removeCallback(callback: Callback) {
+ callbacks.remove(callback)
+ }
+
+ fun sendUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) {
+ callbacks.forEach {
+ it.onUnlockedScreenOffProgressUpdate(linear, eased)
+ }
+ }
+
+/**
* Whether we're doing the light reveal animation or we're done with that and animating in the
* AOD UI.
*/
@@ -228,6 +272,10 @@
return lightRevealAnimationPlaying || aodUiAnimationPlaying
}
+ fun shouldAnimateInKeyguard(): Boolean {
+ return shouldAnimateInKeyguard
+ }
+
/**
* Whether the light reveal animation is playing. The second part of the screen off animation,
* where AOD animates in, might still be playing if this returns false.
@@ -235,4 +283,8 @@
fun isScreenOffLightRevealAnimationPlaying(): Boolean {
return lightRevealAnimationPlaying
}
-}
\ No newline at end of file
+
+ interface Callback {
+ fun onUnlockedScreenOffProgressUpdate(linear: Float, eased: Float)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 6982631..80a0a98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -104,7 +104,16 @@
}
}
+ // Fix for b/199600334
+ override fun onEntryCleanUp(entry: NotificationEntry) {
+ removeChipIfNeeded(entry)
+ }
+
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ removeChipIfNeeded(entry)
+ }
+
+ private fun removeChipIfNeeded(entry: NotificationEntry) {
if (entry.sbn.key == callNotificationInfo?.key) {
removeChip()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index ab58286..6d6320e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -40,6 +40,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
+import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
@@ -68,6 +69,7 @@
private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final Executor mMainExecutor;
private @Nullable WifiPickerTracker mWifiPickerTracker;
@@ -84,6 +86,7 @@
WifiPickerTrackerFactory wifiPickerTrackerFactory
) {
mUserManager = userManager;
+ mUserTracker = userTracker;
mCurrentUser = userTracker.getUserId();
mMainExecutor = mainExecutor;
mWifiPickerTrackerFactory = wifiPickerTrackerFactory;
@@ -118,6 +121,11 @@
new UserHandle(mCurrentUser));
}
+ public boolean canConfigMobileData() {
+ return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin();
+ }
+
public void onUserSwitched(int newUserId) {
mCurrentUser = newUserId;
}
@@ -157,6 +165,15 @@
}
@Override
+ public MergedCarrierEntry getMergedCarrierEntry() {
+ if (mWifiPickerTracker == null) {
+ fireAcccessPointsCallback(Collections.emptyList());
+ return null;
+ }
+ return mWifiPickerTracker.getMergedCarrierEntry();
+ }
+
+ @Override
public int getIcon(WifiEntry ap) {
int level = ap.getLevel();
return ICONS[Math.max(0, level)];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index a0edc7c..1e52511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -149,7 +149,7 @@
private void reinflate() {
int index = mStatusBarWindow.indexOfChild(mBrightnessMirror);
mStatusBarWindow.removeView(mBrightnessMirror);
- mBrightnessMirror = (FrameLayout) LayoutInflater.from(mBrightnessMirror.getContext())
+ mBrightnessMirror = (FrameLayout) LayoutInflater.from(mStatusBarWindow.getContext())
.inflate(R.layout.brightness_mirror_container, mStatusBarWindow, false);
mToggleSliderController = setMirrorLayout();
mStatusBarWindow.addView(mBrightnessMirror, index);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 5e70d0d..d838a05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardVisibilityHelper;
@@ -56,7 +57,7 @@
* Manages the user switch on the Keyguard that is used for opening the QS user panel.
*/
@KeyguardUserSwitcherScope
-public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> {
+public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> {
private static final String TAG = "KeyguardQsUserSwitchController";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -76,6 +77,7 @@
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final KeyguardUserDetailAdapter mUserDetailAdapter;
private NotificationPanelViewController mNotificationPanelViewController;
+ private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
// State info for the user switch and keyguard
@@ -111,7 +113,7 @@
@Inject
public KeyguardQsUserSwitchController(
- UserAvatarView view,
+ FrameLayout view,
Context context,
@Main Resources resources,
ScreenLifecycle screenLifecycle,
@@ -143,6 +145,7 @@
protected void onInit() {
super.onInit();
if (DEBUG) Log.d(TAG, "onInit");
+ mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
@@ -150,11 +153,10 @@
}
};
- mView.setOnClickListener(v -> {
+ mUserAvatarView.setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
-
if (isListAnimating()) {
return;
}
@@ -163,7 +165,7 @@
openQsUserPanel();
});
- mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
@@ -237,12 +239,12 @@
R.string.accessibility_multi_user_switch_switcher);
}
- if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
- mView.setContentDescription(contentDescription);
+ if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) {
+ mUserAvatarView.setContentDescription(contentDescription);
}
int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
- mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
+ mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
}
Drawable getCurrentUserIcon() {
@@ -269,7 +271,7 @@
* Get the height of the keyguard user switcher view when closed.
*/
public int getUserIconHeight() {
- return mView.getHeight();
+ return mUserAvatarView.getHeight();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index fcfc967..7057618 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -245,5 +245,11 @@
* animation.
*/
default void onKeyguardDismissAmountChanged() {}
+
+ /**
+ * Triggered when the notification panel is starting or has finished
+ * fading away on transition to an app.
+ */
+ default void onLaunchTransitionFadingAwayChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 64750bd..f787ecf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -343,6 +343,7 @@
@Override
public void setLaunchTransitionFadingAway(boolean fadingAway) {
mLaunchTransitionFadingAway = fadingAway;
+ new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
index 20b66f0..cd8894c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -103,7 +103,7 @@
}
}
- if (animate) {
+ if (animate && userItemViews.length > 1) {
// AnimationUtils will immediately hide/show the first item in the array. Since the
// first view is the current user, we want to manage its visibility separately.
// Set first item to null so AnimationUtils ignores it.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index ef2ca98..eeea699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -23,6 +23,7 @@
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
@@ -223,9 +224,11 @@
void addAccessPointCallback(AccessPointCallback callback);
void removeAccessPointCallback(AccessPointCallback callback);
void scanForAccessPoints();
+ MergedCarrierEntry getMergedCarrierEntry();
int getIcon(WifiEntry ap);
boolean connect(WifiEntry ap);
boolean canConfigWifi();
+ boolean canConfigMobileData();
public interface AccessPointCallback {
void onAccessPointsChanged(List<WifiEntry> accessPoints);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c49de7a..af0d413 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -68,9 +68,12 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.qs.tiles.dialog.InternetDialogUtil;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -191,6 +194,8 @@
private boolean mUserSetup;
private boolean mSimDetected;
private boolean mForceCellularValidated;
+ private InternetDialogFactory mInternetDialogFactory;
+ private Handler mMainHandler;
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -221,7 +226,9 @@
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
FeatureFlags featureFlags,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Main Handler handler,
+ InternetDialogFactory internetDialogFactory) {
this(context, connectivityManager,
telephonyManager,
telephonyListenerManager,
@@ -242,6 +249,8 @@
featureFlags,
dumpManager);
mReceiverHandler.post(mRegisterListeners);
+ mMainHandler = handler;
+ mInternetDialogFactory = internetDialogFactory;
}
@VisibleForTesting
@@ -480,6 +489,9 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ if (InternetDialogUtil.isProviderModelEnabled(mContext)) {
+ filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
+ }
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
@@ -788,6 +800,10 @@
mConfig = Config.readConfig(mContext);
mReceiverHandler.post(this::handleConfigurationChanged);
break;
+ case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
+ mMainHandler.post(() -> mInternetDialogFactory.create(true,
+ mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi()));
+ break;
default:
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 22fd93e..2d47c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -71,7 +71,11 @@
if (mNextAlarm != null) {
pw.println(new Date(mNextAlarm.getTriggerTime()));
pw.print(" PendingIntentPkg=");
- pw.println(mNextAlarm.getShowIntent().getCreatorPackage());
+ if (mNextAlarm.getShowIntent() != null) {
+ pw.println(mNextAlarm.getShowIntent().getCreatorPackage());
+ } else {
+ pw.println("showIntent=null");
+ }
} else {
pw.println("null");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 84d7c05..5d7d480 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -77,7 +77,6 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -204,7 +203,7 @@
final int stroke = colorized ? mContext.getResources().getDimensionPixelSize(
R.dimen.remote_input_view_text_stroke) : 0;
if (colorized) {
- final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+ final boolean dark = Notification.Builder.isColorDark(backgroundColor);
final int foregroundColor = dark ? Color.WHITE : Color.BLACK;
final int inverseColor = dark ? Color.BLACK : Color.WHITE;
editBgColor = backgroundColor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index f258fb1..1158324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -23,6 +23,7 @@
int getRotationLockOrientation();
boolean isRotationLockAffordanceVisible();
boolean isRotationLocked();
+ boolean isCameraRotationEnabled();
void setRotationLocked(boolean locked);
void setRotationLockedAtAngle(boolean locked, int rotation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 53d68d0..c185928 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -18,11 +18,13 @@
import android.content.Context;
import android.os.UserHandle;
+import android.provider.Settings.Secure;
import androidx.annotation.NonNull;
import com.android.internal.view.RotationPolicy;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -32,20 +34,22 @@
@SysUISingleton
public final class RotationLockControllerImpl implements RotationLockController {
private final Context mContext;
+ private final SecureSettings mSecureSettings;
private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
new CopyOnWriteArrayList<RotationLockControllerCallback>();
private final RotationPolicy.RotationPolicyListener mRotationPolicyListener =
new RotationPolicy.RotationPolicyListener() {
- @Override
- public void onChange() {
- notifyChanged();
- }
- };
+ @Override
+ public void onChange() {
+ notifyChanged();
+ }
+ };
@Inject
- public RotationLockControllerImpl(Context context) {
+ public RotationLockControllerImpl(Context context, SecureSettings secureSettings) {
mContext = context;
+ mSecureSettings = secureSettings;
setListening(true);
}
@@ -68,11 +72,16 @@
return RotationPolicy.isRotationLocked(mContext);
}
+ public boolean isCameraRotationEnabled() {
+ return mSecureSettings.getIntForUser(Secure.CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT)
+ == 1;
+ }
+
public void setRotationLocked(boolean locked) {
RotationPolicy.setRotationLock(mContext, locked);
}
- public void setRotationLockedAtAngle(boolean locked, int rotation){
+ public void setRotationLockedAtAngle(boolean locked, int rotation) {
RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 41b1dd1..4e33529 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -628,7 +628,7 @@
mCurrentBackgroundColor = backgroundColor;
mCurrentColorized = colorized;
- final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+ final boolean dark = Notification.Builder.isColorDark(backgroundColor);
mCurrentTextColor = ContrastColorUtil.ensureTextContrast(
dark ? mDefaultTextColorDarkBg : mDefaultTextColor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
new file mode 100644
index 0000000..ae9d9ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.Context
+import android.text.StaticLayout
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.R
+
+/**
+ * View for showing a date that can toggle between two different formats depending on size.
+ *
+ * If no pattern can fit, it will display empty.
+ *
+ * @see R.styleable.VariableDateView_longDatePattern
+ * @see R.styleable.VariableDateView_shortDatePattern
+ */
+class VariableDateView(context: Context, attrs: AttributeSet) : TextView(context, attrs) {
+
+ val longerPattern: String
+ val shorterPattern: String
+
+ init {
+ val a = context.theme.obtainStyledAttributes(
+ attrs,
+ R.styleable.VariableDateView,
+ 0, 0)
+ longerPattern = a.getString(R.styleable.VariableDateView_longDatePattern)
+ ?: context.getString(R.string.system_ui_date_pattern)
+ shorterPattern = a.getString(R.styleable.VariableDateView_shortDatePattern)
+ ?: context.getString(R.string.abbrev_month_day_no_year)
+
+ a.recycle()
+ }
+
+ /**
+ * Freeze the pattern switching
+ *
+ * Use during animations if the container will change its size but this view should not change
+ */
+ var freezeSwitching = false
+
+ private var onMeasureListener: OnMeasureListener? = null
+
+ fun onAttach(listener: OnMeasureListener?) {
+ onMeasureListener = listener
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val availableWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingStart - paddingEnd
+ if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED && !freezeSwitching) {
+ onMeasureListener?.onMeasureAction(availableWidth)
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+
+ fun getDesiredWidthForText(text: CharSequence): Float {
+ return StaticLayout.getDesiredWidth(text, paint)
+ }
+
+ interface OnMeasureListener {
+ fun onMeasureAction(availableWidth: Int)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
new file mode 100644
index 0000000..99d84c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.icu.text.DateFormat
+import android.icu.text.DisplayContext
+import android.icu.util.Calendar
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dependency
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.ViewController
+import com.android.systemui.util.time.SystemClock
+import java.text.FieldPosition
+import java.text.ParsePosition
+import java.util.Date
+import java.util.Locale
+import javax.inject.Inject
+import javax.inject.Named
+
+@VisibleForTesting
+internal fun getTextForFormat(date: Date?, format: DateFormat): String {
+ return if (format === EMPTY_FORMAT) { // Check if same object
+ ""
+ } else format.format(date)
+}
+
+@VisibleForTesting
+internal fun getFormatFromPattern(pattern: String?): DateFormat {
+ if (TextUtils.equals(pattern, "")) {
+ return EMPTY_FORMAT
+ }
+ val l = Locale.getDefault()
+ val format = DateFormat.getInstanceForSkeleton(pattern, l)
+ format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
+ return format
+}
+
+private val EMPTY_FORMAT: DateFormat = object : DateFormat() {
+ override fun format(
+ cal: Calendar,
+ toAppendTo: StringBuffer,
+ fieldPosition: FieldPosition
+ ): StringBuffer? {
+ return null
+ }
+
+ override fun parse(text: String, cal: Calendar, pos: ParsePosition) {}
+}
+
+private const val DEBUG = false
+private const val TAG = "VariableDateViewController"
+
+class VariableDateViewController(
+ private val systemClock: SystemClock,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val timeTickHandler: Handler,
+ view: VariableDateView
+) : ViewController<VariableDateView>(view) {
+
+ private var dateFormat: DateFormat? = null
+ private var datePattern = view.longerPattern
+ set(value) {
+ if (field == value) return
+ field = value
+ dateFormat = null
+ if (isAttachedToWindow) {
+ post(::updateClock)
+ }
+ }
+ private var lastWidth = Integer.MAX_VALUE
+ private var lastText = ""
+ private var currentTime = Date()
+
+ // View class easy accessors
+ private val longerPattern: String
+ get() = mView.longerPattern
+ private val shorterPattern: String
+ get() = mView.shorterPattern
+ private fun post(block: () -> Unit) = mView.handler?.post(block)
+
+ private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ // If the handler is null, it means we received a broadcast while the view has not
+ // finished being attached or in the process of being detached.
+ // In that case, do not post anything.
+ val handler = mView.handler ?: return
+ val action = intent.action
+ if (
+ Intent.ACTION_TIME_TICK == action ||
+ Intent.ACTION_TIME_CHANGED == action ||
+ Intent.ACTION_TIMEZONE_CHANGED == action ||
+ Intent.ACTION_LOCALE_CHANGED == action
+ ) {
+ if (
+ Intent.ACTION_LOCALE_CHANGED == action ||
+ Intent.ACTION_TIMEZONE_CHANGED == action
+ ) {
+ // need to get a fresh date format
+ handler.post { dateFormat = null }
+ }
+ handler.post(::updateClock)
+ }
+ }
+ }
+
+ private val onMeasureListener = object : VariableDateView.OnMeasureListener {
+ override fun onMeasureAction(availableWidth: Int) {
+ if (availableWidth != lastWidth) {
+ // maybeChangeFormat will post if the pattern needs to change.
+ maybeChangeFormat(availableWidth)
+ lastWidth = availableWidth
+ }
+ }
+ }
+
+ override fun onViewAttached() {
+ val filter = IntentFilter().apply {
+ addAction(Intent.ACTION_TIME_TICK)
+ addAction(Intent.ACTION_TIME_CHANGED)
+ addAction(Intent.ACTION_TIMEZONE_CHANGED)
+ addAction(Intent.ACTION_LOCALE_CHANGED)
+ }
+
+ broadcastDispatcher.registerReceiver(intentReceiver, filter,
+ HandlerExecutor(timeTickHandler), UserHandle.SYSTEM)
+
+ post(::updateClock)
+ mView.onAttach(onMeasureListener)
+ }
+
+ override fun onViewDetached() {
+ dateFormat = null
+ mView.onAttach(null)
+ broadcastDispatcher.unregisterReceiver(intentReceiver)
+ }
+
+ private fun updateClock() {
+ if (dateFormat == null) {
+ dateFormat = getFormatFromPattern(datePattern)
+ }
+
+ currentTime.time = systemClock.currentTimeMillis()
+
+ val text = getTextForFormat(currentTime, dateFormat!!)
+ if (text != lastText) {
+ mView.setText(text)
+ lastText = text
+ }
+ }
+
+ private fun maybeChangeFormat(availableWidth: Int) {
+ if (mView.freezeSwitching ||
+ availableWidth > lastWidth && datePattern == longerPattern ||
+ availableWidth < lastWidth && datePattern == ""
+ ) {
+ // Nothing to do
+ return
+ }
+ if (DEBUG) Log.d(TAG, "Width changed. Maybe changing pattern")
+ // Start with longer pattern and see what fits
+ var text = getTextForFormat(currentTime, getFormatFromPattern(longerPattern))
+ var length = mView.getDesiredWidthForText(text)
+ if (length <= availableWidth) {
+ changePattern(longerPattern)
+ return
+ }
+
+ text = getTextForFormat(currentTime, getFormatFromPattern(shorterPattern))
+ length = mView.getDesiredWidthForText(text)
+ if (length <= availableWidth) {
+ changePattern(shorterPattern)
+ return
+ }
+
+ changePattern("")
+ }
+
+ private fun changePattern(newPattern: String) {
+ if (newPattern.equals(datePattern)) return
+ if (DEBUG) Log.d(TAG, "Changing pattern to $newPattern")
+ datePattern = newPattern
+ }
+
+ class Factory @Inject constructor(
+ private val systemClock: SystemClock,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
+ ) {
+ fun create(view: VariableDateView): VariableDateViewController {
+ return VariableDateViewController(
+ systemClock,
+ broadcastDispatcher,
+ handler,
+ view
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index c3b4fbe..e2d0bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -132,14 +132,18 @@
/* Target package for each overlay category. */
private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>();
private final OverlayManager mOverlayManager;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
+ private final Executor mMainExecutor;
private final String mLauncherPackage;
private final String mThemePickerPackage;
- public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor,
+ public ThemeOverlayApplier(OverlayManager overlayManager,
+ Executor bgExecutor,
+ Executor mainExecutor,
String launcherPackage, String themePickerPackage, DumpManager dumpManager) {
mOverlayManager = overlayManager;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
mLauncherPackage = launcherPackage;
mThemePickerPackage = themePickerPackage;
mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
@@ -170,12 +174,12 @@
* Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
* affect sysui will also be applied to the system user.
*/
- void applyCurrentUserOverlays(
+ public void applyCurrentUserOverlays(
Map<String, OverlayIdentifier> categoryToPackage,
FabricatedOverlay[] pendingCreation,
int currentUser,
Set<UserHandle> managedProfiles) {
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
// Disable all overlays that have not been specified in the user setting.
final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
index 98b4209e..bfa50bc 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
@@ -63,6 +64,8 @@
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ getWindow().addPrivateFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
Intent intent = getIntent();
mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index de5a363..14190fa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -23,8 +23,9 @@
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import javax.inject.Inject;
@@ -34,39 +35,54 @@
*/
@SysUISingleton
public class CarrierConfigTracker extends BroadcastReceiver {
- private final SparseArray<Boolean> mCallStrengthConfigs = new SparseArray<>();
- private final SparseArray<Boolean> mNoCallingConfigs = new SparseArray<>();
+ private final SparseBooleanArray mCallStrengthConfigs = new SparseBooleanArray();
+ private final SparseBooleanArray mNoCallingConfigs = new SparseBooleanArray();
+ private final SparseBooleanArray mCarrierProvisionsWifiMergedNetworks =
+ new SparseBooleanArray();
private final CarrierConfigManager mCarrierConfigManager;
private boolean mDefaultCallStrengthConfigLoaded;
private boolean mDefaultCallStrengthConfig;
private boolean mDefaultNoCallingConfigLoaded;
private boolean mDefaultNoCallingConfig;
+ private boolean mDefaultCarrierProvisionsWifiMergedNetworksLoaded;
+ private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
@Inject
- public CarrierConfigTracker(Context context) {
+ public CarrierConfigTracker(Context context, BroadcastDispatcher broadcastDispatcher) {
mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
- context.registerReceiver(
+ broadcastDispatcher.registerReceiver(
this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
}
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction() == CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) {
- int subId = intent.getIntExtra(
- CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- return;
- }
- PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
- if (b != null) {
- boolean hideNoCallingConfig = b.getBoolean(
- CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL);
- boolean displayCallStrengthIcon = b.getBoolean(
- CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL);
- mCallStrengthConfigs.put(subId, displayCallStrengthIcon);
- mNoCallingConfigs.put(subId, hideNoCallingConfig);
- }
+ if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ return;
+ }
+
+ final int subId = intent.getIntExtra(
+ CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ return;
+ }
+
+ final PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+ if (config == null) {
+ return;
+ }
+
+ synchronized (mCallStrengthConfigs) {
+ mCallStrengthConfigs.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL));
+ }
+ synchronized (mNoCallingConfigs) {
+ mNoCallingConfigs.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL));
+ }
+ synchronized (mCarrierProvisionsWifiMergedNetworks) {
+ mCarrierProvisionsWifiMergedNetworks.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL));
}
}
@@ -74,8 +90,10 @@
* Returns the KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL value for the given subId.
*/
public boolean getCallStrengthConfig(int subId) {
- if (mCallStrengthConfigs.indexOfKey(subId) >= 0) {
- return mCallStrengthConfigs.get(subId);
+ synchronized (mCallStrengthConfigs) {
+ if (mCallStrengthConfigs.indexOfKey(subId) >= 0) {
+ return mCallStrengthConfigs.get(subId);
+ }
}
if (!mDefaultCallStrengthConfigLoaded) {
mDefaultCallStrengthConfig =
@@ -90,8 +108,10 @@
* Returns the KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL value for the given subId.
*/
public boolean getNoCallingConfig(int subId) {
- if (mNoCallingConfigs.indexOfKey(subId) >= 0) {
- return mNoCallingConfigs.get(subId);
+ synchronized (mNoCallingConfigs) {
+ if (mNoCallingConfigs.indexOfKey(subId) >= 0) {
+ return mNoCallingConfigs.get(subId);
+ }
}
if (!mDefaultNoCallingConfigLoaded) {
mDefaultNoCallingConfig =
@@ -101,4 +121,22 @@
}
return mDefaultNoCallingConfig;
}
+
+ /**
+ * Returns the KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL value for the given subId.
+ */
+ public boolean getCarrierProvisionsWifiMergedNetworksBool(int subId) {
+ synchronized (mCarrierProvisionsWifiMergedNetworks) {
+ if (mCarrierProvisionsWifiMergedNetworks.indexOfKey(subId) >= 0) {
+ return mCarrierProvisionsWifiMergedNetworks.get(subId);
+ }
+ }
+ if (!mDefaultCarrierProvisionsWifiMergedNetworksLoaded) {
+ mDefaultCarrierProvisionsWifiMergedNetworks =
+ CarrierConfigManager.getDefaultConfig().getBoolean(
+ CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL);
+ mDefaultCarrierProvisionsWifiMergedNetworksLoaded = true;
+ }
+ return mDefaultCarrierProvisionsWifiMergedNetworks;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 90e022a5..bd11039 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -87,15 +87,23 @@
&& (mLastPrimaryEvent == null
|| !mLastPrimaryEvent.getBelow()
|| !event.getBelow())) {
- mSecondaryThresholdSensor.pause();
+ chooseSensor();
if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
// Only check the secondary as long as the primary thinks we're near.
- mCancelSecondaryRunnable = null;
+ if (mCancelSecondaryRunnable != null) {
+ mCancelSecondaryRunnable.run();
+ mCancelSecondaryRunnable = null;
+ }
return;
} else {
// Check this sensor again in a moment.
- mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(
- mSecondaryThresholdSensor::resume, SECONDARY_PING_INTERVAL_MS);
+ mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
+ // This is safe because we know that mSecondaryThresholdSensor
+ // is loaded, otherwise we wouldn't be here.
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.resume();
+ },
+ SECONDARY_PING_INTERVAL_MS);
}
}
logDebug("Secondary sensor event: " + event.getBelow() + ".");
@@ -159,12 +167,8 @@
* of what is reported by the primary sensor.
*/
public void setSecondarySafe(boolean safe) {
- mSecondarySafe = safe;
- if (!mSecondarySafe) {
- mSecondaryThresholdSensor.pause();
- } else {
- mSecondaryThresholdSensor.resume();
- }
+ mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
+ chooseSensor();
}
/**
@@ -209,16 +213,30 @@
return;
}
if (!mInitializedListeners) {
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.pause();
mPrimaryThresholdSensor.register(mPrimaryEventListener);
- if (!mSecondarySafe) {
- mSecondaryThresholdSensor.pause();
- }
mSecondaryThresholdSensor.register(mSecondaryEventListener);
mInitializedListeners = true;
}
logDebug("Registering sensor listener");
- mPrimaryThresholdSensor.resume();
+
mRegistered = true;
+ chooseSensor();
+ }
+
+ private void chooseSensor() {
+ mExecution.assertIsMainThread();
+ if (!mRegistered || mPaused || mListeners.isEmpty()) {
+ return;
+ }
+ if (mSecondarySafe) {
+ mSecondaryThresholdSensor.resume();
+ mPrimaryThresholdSensor.pause();
+ } else {
+ mPrimaryThresholdSensor.resume();
+ mSecondaryThresholdSensor.pause();
+ }
}
/**
@@ -312,7 +330,7 @@
}
if (!mSecondarySafe && !event.getBelow()) {
- mSecondaryThresholdSensor.pause();
+ chooseSensor();
}
mLastEvent = event;
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 2b4b49b..9917777 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -116,7 +116,7 @@
if (toolbar != null) {
setActionBar(toolbar);
}
- setTitle("");
+ getActionBar().setDisplayShowTitleEnabled(false);
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeAsUpIndicator(getHomeIndicatorDrawable());
getActionBar().setHomeActionContentDescription(R.string.accessibility_desc_close);
@@ -220,6 +220,12 @@
}
@Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.wallet_activity_options_menu, menu);
return super.onCreateOptionsMenu(menu);
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 5f1fe92..763a5cb 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -93,6 +93,13 @@
<activity android:name="com.android.systemui.screenshot.RecyclerViewActivity"
android:exported="false" />
+ <!-- started from UsbDeviceSettingsManager -->
+ <activity android:name=".usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
+ android:exported="false"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true" />
+
<provider
android:name="androidx.startup.InitializationProvider"
tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 06b0bb2..8077dea 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -119,6 +119,7 @@
when(mNotificationIcons.getLayoutParams()).thenReturn(
mock(RelativeLayout.LayoutParams.class));
when(mView.getContext()).thenReturn(getContext());
+ when(mView.getResources()).thenReturn(mResources);
when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
@@ -127,7 +128,6 @@
when(mLargeClockView.getContext()).thenReturn(getContext());
when(mView.isAttachedToWindow()).thenReturn(true);
- when(mResources.getString(anyInt())).thenReturn("h:mm");
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController = new KeyguardClockSwitchController(
mView,
@@ -142,7 +142,8 @@
mBypassController,
mSmartspaceController,
mKeyguardUnlockAnimationController,
- mSmartSpaceTransitionController
+ mSmartSpaceTransitionController,
+ mResources
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 39d5314..8dd5d6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -565,8 +565,9 @@
credentialAllowed,
true /* requireConfirmation */,
0 /* userId */,
- "testPackage",
0 /* operationId */,
+ "testPackage",
+ 1 /* requestId */,
BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT);
}
@@ -612,7 +613,7 @@
@Override
protected AuthDialog buildDialog(PromptInfo promptInfo,
boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
- String opPackageName, boolean skipIntro, long operationId,
+ String opPackageName, boolean skipIntro, long operationId, long requestId,
@BiometricManager.BiometricMultiSensorMode int multiSensorConfig) {
mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index d87a26b..f2f0029 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -19,16 +19,23 @@
import android.graphics.PointF
import android.hardware.biometrics.BiometricSourceType
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.StatusBar
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,6 +49,8 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+
@SmallTest
@RunWith(AndroidTestingRunner::class)
class AuthRippleControllerTest : SysuiTestCase() {
@@ -52,26 +61,39 @@
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var bypassController: KeyguardBypassController
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
+ @Mock private lateinit var udfpsController: UdfpsController
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var lightRevealScrim: LightRevealScrim
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ `when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
+
controller = AuthRippleController(
statusBar,
context,
authController,
configurationController,
keyguardUpdateMonitor,
+ keyguardStateController,
+ wakefulnessLifecycle,
commandRegistry,
notificationShadeWindowController,
bypassController,
biometricUnlockController,
+ udfpsControllerProvider,
+ statusBarStateController,
rippleView
)
controller.init()
+ `when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim)
}
@Test
@@ -93,7 +115,7 @@
// THEN update sensor location and show ripple
verify(rippleView).setSensorLocation(fpsLocation)
- verify(rippleView).startRipple(any(), any())
+ verify(rippleView).startUnlockedRipple(any())
}
@Test
@@ -114,7 +136,7 @@
false /* isStrongBiometric */)
// THEN no ripple
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
@@ -135,7 +157,7 @@
false /* isStrongBiometric */)
// THEN no ripple
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
@@ -159,7 +181,7 @@
// THEN show ripple
verify(rippleView).setSensorLocation(faceLocation)
- verify(rippleView).startRipple(any(), any())
+ verify(rippleView).startUnlockedRipple(any())
}
@Test
@@ -179,7 +201,7 @@
false /* isStrongBiometric */)
// THEN no ripple
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
@@ -194,7 +216,7 @@
0 /* userId */,
BiometricSourceType.FACE /* type */,
false /* isStrongBiometric */)
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
@@ -209,7 +231,39 @@
0 /* userId */,
BiometricSourceType.FINGERPRINT /* type */,
false /* isStrongBiometric */)
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
+ }
+
+ @Test
+ fun registersAndDeregisters() {
+ controller.onViewAttached()
+ val captor = ArgumentCaptor
+ .forClass(KeyguardStateController.Callback::class.java)
+ verify(keyguardStateController).addCallback(captor.capture())
+ val captor2 = ArgumentCaptor
+ .forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor2.capture())
+ controller.onViewDetached()
+ verify(keyguardStateController).removeCallback(any())
+ verify(wakefulnessLifecycle).removeObserver(any())
+ }
+
+ @Test
+ @RunWithLooper(setAsMainLooper = true)
+ fun testAnimatorRunWhenWakeAndUnlock() {
+ val fpsLocation = PointF(5f, 5f)
+ `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
+ controller.onViewAttached()
+ `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
+
+ controller.showRipple(BiometricSourceType.FINGERPRINT)
+ assertTrue("reveal didn't start on keyguardFadingAway",
+ controller.startLightRevealScrimOnKeyguardFadingAway)
+ `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
+ controller.onKeyguardFadingAwayChanged()
+ assertFalse("reveal triggers multiple times",
+ controller.startLightRevealScrimOnKeyguardFadingAway)
}
@Test
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 2120b0e..04ebee89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
+
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -55,7 +57,6 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -63,12 +64,14 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.time.SystemClock;
import org.junit.Before;
import org.junit.Rule;
@@ -120,8 +123,6 @@
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
@Mock
private FalsingManager mFalsingManager;
@@ -147,6 +148,10 @@
private Handler mHandler;
@Mock
private ConfigurationController mConfigurationController;
+ @Mock
+ private SystemClock mSystemClock;
+ @Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private FakeExecutor mFgExecutor;
@@ -186,6 +191,7 @@
when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view, null))
.thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD
when(mEnrollView.getContext()).thenReturn(mContext);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -216,7 +222,6 @@
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
- mKeyguardViewMediator,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
@@ -229,7 +234,9 @@
mKeyguardBypassController,
mDisplayManager,
mHandler,
- mConfigurationController);
+ mConfigurationController,
+ mSystemClock,
+ mUnlockedScreenOffAnimationController);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -413,6 +420,21 @@
}
@Test
+ public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
+ // GIVEN overlay was showing and the udfps bouncer is showing
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+ IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+
+ // WHEN the overlay is hidden
+ mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mFgExecutor.runAllReady();
+
+ // THEN the udfps bouncer is reset
+ verify(mStatusBarKeyguardViewManager).resetAlternateAuth(eq(true));
+ }
+
+ @Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
@@ -456,11 +478,12 @@
@Test
public void aodInterrupt() throws RemoteException {
- // GIVEN that the overlay is showing and screen is on
+ // GIVEN that the overlay is showing and screen is on and fp is running
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
// THEN illumination begins
@@ -478,6 +501,7 @@
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
// WHEN it is cancelled
@@ -493,6 +517,7 @@
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
// WHEN it times out
@@ -511,6 +536,23 @@
mFgExecutor.runAllReady();
// WHEN aod interrupt is received
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+ mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+
+ // THEN no illumination because screen is off
+ verify(mUdfpsView, never()).startIllumination(any());
+ }
+
+ @Test
+ public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+ // GIVEN showing overlay
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+ IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+
+ // WHEN aod interrupt is received when the fingerprint service isn't running
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// THEN no illumination because screen is off
@@ -544,5 +586,10 @@
eq(mUdfpsController.EFFECT_CLICK),
eq("udfps-onStart"),
eq(UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES));
+
+ // THEN make sure vibration attributes has so that it always will play the haptic,
+ // even in battery saver mode
+ assertEquals(USAGE_ASSISTANCE_ACCESSIBILITY,
+ UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES.getUsage());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0c03a51..4b35de1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,8 +18,13 @@
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;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,7 +35,6 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -39,8 +43,11 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -50,6 +57,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -75,11 +84,16 @@
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
private KeyguardViewMediator mKeyguardViewMediator;
@Mock
private ConfigurationController mConfigurationController;
@Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ @Mock
private UdfpsController mUdfpsController;
+ private FakeSystemClock mSystemClock = new FakeSystemClock();
private UdfpsKeyguardViewController mController;
@@ -88,14 +102,15 @@
private StatusBarStateController.StateListener mStatusBarStateListener;
@Captor private ArgumentCaptor<StatusBar.ExpansionChangedListener> mExpansionListenerCaptor;
- private StatusBar.ExpansionChangedListener mExpansionListener;
+ private List<StatusBar.ExpansionChangedListener> mExpansionListeners;
@Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
mAltAuthInterceptorCaptor;
private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
- @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateMonitorCallbackCaptor;
- private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+ @Captor private ArgumentCaptor<KeyguardStateController.Callback>
+ mKeyguardStateControllerCallbackCaptor;
+ private KeyguardStateController.Callback mKeyguardStateControllerCallback;
@Before
public void setUp() {
@@ -109,18 +124,19 @@
mStatusBar,
mStatusBarKeyguardViewManager,
mKeyguardUpdateMonitor,
- mExecutor,
mDumpManager,
- mKeyguardViewMediator,
mLockscreenShadeTransitionController,
mConfigurationController,
+ mSystemClock,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
mUdfpsController);
}
@Test
public void testRegistersExpansionChangedListenerOnAttached() {
mController.onViewAttached();
- captureExpansionListener();
+ captureExpansionListeners();
}
@Test
@@ -149,11 +165,15 @@
public void testListenersUnregisteredOnDetached() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
+ captureExpansionListeners();
+ captureKeyguardStateControllerCallback();
mController.onViewDetached();
verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
- verify(mStatusBar).removeExpansionChangedListener(mExpansionListener);
+ for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) {
+ verify(mStatusBar).removeExpansionChangedListener(listener);
+ }
+ verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
}
@Test
@@ -172,7 +192,6 @@
public void testShouldPauseAuthBouncerShowing() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
@@ -183,7 +202,6 @@
public void testShouldNotPauseAuthOnKeyguard() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
@@ -191,10 +209,25 @@
}
@Test
+ public void testShouldPauseAuthIsLaunchTransitionFadingAway() {
+ // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ // WHEN isLaunchTransitionFadingAway=true
+ captureKeyguardStateControllerCallback();
+ when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true);
+ mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged();
+
+ // THEN pause auth
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
public void testShouldPauseAuthOnShadeLocked() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
@@ -205,7 +238,6 @@
public void testShouldPauseAuthOnShade() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
// WHEN not on keyguard yet (shade = home)
sendStatusBarStateChanged(StatusBarState.SHADE);
@@ -218,7 +250,6 @@
public void testShouldPauseAuthAnimatingScreenOffFromShade() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
// WHEN transitioning from home/shade => keyguard + animating screen off
mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD);
@@ -232,7 +263,6 @@
public void testDoNotPauseAuthAnimatingScreenOffFromLS() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
// WHEN animating screen off transition from LS => AOD
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
@@ -273,6 +303,127 @@
verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
}
+ @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();
+ captureExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ reset(mView);
+
+ // WHEN status bar expansion is 0
+ updateStatusBarExpansion(0, true);
+
+ // THEN alpha is 0
+ verify(mView).setUnpausedAlpha(0);
+ }
+
+ @Test
+ public void testShowUdfpsBouncer() {
+ // GIVEN view is attached and status bar expansion is 0
+ mController.onViewAttached();
+ captureExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ captureAltAuthInterceptor();
+ updateStatusBarExpansion(0, true);
+ reset(mView);
+ when(mView.getContext()).thenReturn(mResourceContext);
+ when(mResourceContext.getString(anyInt())).thenReturn("test string");
+
+ // WHEN status bar expansion is 0 but udfps bouncer is requested
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // THEN alpha is 255
+ verify(mView).setUnpausedAlpha(255);
+ }
+
+ @Test
+ public void testTransitionToFullShadeProgress() {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached();
+ captureExpansionListeners();
+ updateStatusBarExpansion(1f, true);
+ reset(mView);
+
+ // WHEN we're transitioning to the full shade
+ float transitionProgress = .6f;
+ mController.setTransitionToFullShadeProgress(transitionProgress);
+
+ // THEN alpha is between 0 and 255
+ verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
+ }
+
+ @Test
+ public void testShowUdfpsBouncer_transitionToFullShadeProgress() {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached();
+ captureExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ captureAltAuthInterceptor();
+ updateStatusBarExpansion(1f, true);
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+ reset(mView);
+
+ // WHEN we're transitioning to the full shade
+ mController.setTransitionToFullShadeProgress(1.0f);
+
+ // THEN alpha is 255 (b/c udfps bouncer is requested)
+ verify(mView).setUnpausedAlpha(255);
+ }
+
private void sendStatusBarStateChanged(int statusBarState) {
mStatusBarStateListener.onStateChanged(statusBarState);
}
@@ -282,9 +433,18 @@
mStatusBarStateListener = mStateListenerCaptor.getValue();
}
- private void captureExpansionListener() {
- verify(mStatusBar).addExpansionChangedListener(mExpansionListenerCaptor.capture());
- mExpansionListener = mExpansionListenerCaptor.getValue();
+ private void captureExpansionListeners() {
+ verify(mStatusBar, times(2))
+ .addExpansionChangedListener(mExpansionListenerCaptor.capture());
+ // first (index=0) is from super class, UdfpsAnimationViewController.
+ // second (index=1) is from UdfpsKeyguardViewController
+ mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+ }
+
+ private void updateStatusBarExpansion(float expansion, boolean expanded) {
+ for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) {
+ listener.onExpansionChanged(expansion, expanded);
+ }
}
private void captureAltAuthInterceptor() {
@@ -293,8 +453,11 @@
mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
}
- private void captureKeyguardUpdateMonitorCallback() {
- verify(mKeyguardUpdateMonitor).registerCallback(mUpdateMonitorCallbackCaptor.capture());
- mKeyguardUpdateMonitorCallback = mUpdateMonitorCallbackCaptor.getValue();
+
+
+ private void captureKeyguardStateControllerCallback() {
+ verify(mKeyguardStateController).addCallback(
+ mKeyguardStateControllerCallbackCaptor.capture());
+ mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
}
}
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 4e8b59c..b8d465a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -18,6 +18,7 @@
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
@@ -29,6 +30,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,13 +44,14 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
-import android.view.Display;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -82,6 +85,10 @@
WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
DozeParameters mDozeParameters;
+ @Mock
+ DockManager mDockManager;
+ @Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
@@ -109,9 +116,8 @@
mSensor = fakeSensorManager.getFakeLightSensor();
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
-
- mScreen.onScreenState(Display.STATE_ON);
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
+ mUnlockedScreenOffAnimationController);
}
@Test
@@ -123,18 +129,9 @@
}
@Test
- public void testAod_usesLightSensor() {
- mScreen.onScreenState(Display.STATE_DOZE);
- waitForSensorManager();
-
- mSensor.sendSensorEvent(3);
-
- assertEquals(3, mServiceFake.screenBrightness);
- }
-
- @Test
public void testAod_usesDebugValue() throws Exception {
- mScreen.onScreenState(Display.STATE_DOZE);
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
waitForSensorManager();
Intent intent = new Intent(DozeScreenBrightness.ACTION_AOD_BRIGHTNESS);
@@ -157,10 +154,53 @@
}
@Test
+ public void doze_doesNotUseLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is NOT changed, it's set to the default brightness
+ assertNotSame(3, mServiceFake.screenBrightness);
+ assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void aod_usesLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is updated
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void docked_usesLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ mScreen.transitionTo(DOZE_AOD, DOZE_AOD_DOCKED);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is updated
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
public void testPausingAod_doesNotResetBrightness() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(1);
@@ -175,7 +215,8 @@
public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
+ mUnlockedScreenOffAnimationController);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
reset(mDozeHost);
@@ -201,36 +242,22 @@
}
@Test
- public void testOnScreenStateSetBeforeTransition_stillRegistersSensor() {
- mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- mScreen.onScreenState(Display.STATE_DOZE);
- mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- waitForSensorManager();
-
- mSensor.sendSensorEvent(1);
-
- assertEquals(1, mServiceFake.screenBrightness);
- }
-
- @Test
public void testNullSensor() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
+ mUnlockedScreenOffAnimationController);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
- mScreen.onScreenState(Display.STATE_DOZE);
- mScreen.onScreenState(Display.STATE_OFF);
}
@Test
public void testNoBrightnessDeliveredAfterFinish() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
mScreen.transitionTo(DOZE_AOD, FINISH);
waitForSensorManager();
@@ -243,7 +270,6 @@
public void testNonPositiveBrightness_keepsPreviousBrightnessAndScrim() {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(1);
@@ -257,7 +283,6 @@
public void pausingAod_unblanksAfterSensor() {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
@@ -269,7 +294,6 @@
reset(mDozeHost);
mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
verify(mDozeHost).setAodDimmingScrim(eq(0f));
@@ -279,7 +303,6 @@
public void pausingAod_unblanksIfSensorWasAlwaysReady() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
@@ -296,13 +319,14 @@
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+ when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
// If we're dozing after a timeout, and playing the unlocked screen animation, we should
- // stay at dim brightness, because the screen dims just before timeout.
- assertEquals(mServiceFake.screenBrightness, DIM_BRIGHTNESS);
+ // stay at or below dim brightness, because the screen dims just before timeout.
+ assertTrue(mServiceFake.screenBrightness <= DIM_BRIGHTNESS);
}
@Test
@@ -310,6 +334,7 @@
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+ when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
@@ -324,6 +349,7 @@
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
+ when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(false);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 41d7fd6..3e19cc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -21,6 +21,7 @@
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
import static com.android.systemui.doze.DozeMachine.State.FINISH;
@@ -34,6 +35,7 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,6 +47,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.wakelock.WakeLockFake;
import com.android.systemui.utils.os.FakeHandler;
@@ -56,6 +60,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import javax.inject.Provider;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DozeScreenStateTest extends SysuiTestCase {
@@ -68,17 +74,32 @@
private DozeParameters mDozeParameters;
private WakeLockFake mWakeLock;
private DozeScreenState mScreen;
+ @Mock
+ private Provider<UdfpsController> mUdfpsControllerProvider;
+ @Mock
+ private AuthController mAuthController;
+ @Mock
+ private UdfpsController mUdfpsController;
+ @Mock
+ private DozeLog mDozeLog;
+ @Mock
+ private DozeScreenBrightness mDozeScreenBrightness;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ when(mUdfpsControllerProvider.get()).thenReturn(mUdfpsController);
+ when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+ when(mUdfpsController.isFingerDown()).thenReturn(false);
+
mServiceFake = new DozeServiceFake();
mHandlerFake = new FakeHandler(Looper.getMainLooper());
mWakeLock = new WakeLockFake();
mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
- mWakeLock);
+ mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
+ mDozeScreenBrightness);
}
@Test
@@ -233,4 +254,56 @@
assertEquals(Display.STATE_OFF, mServiceFake.screenState);
}
+ @Test
+ public void testDelayEnterDozeScreenState_whenUdfpsFingerDown() {
+ // GIVEN AOD is initialized
+ when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+ mHandlerFake.setMode(QUEUEING);
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mHandlerFake.dispatchQueuedMessages();
+
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+ // WHEN udfps is activated (fingerDown)
+ when(mUdfpsController.isFingerDown()).thenReturn(true);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // THEN the display screen state doesn't immediately change
+ assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+ // WHEN udfpsController finger is no longer down and the queued messages are run
+ when(mUdfpsController.isFingerDown()).thenReturn(false);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // THEN the display screen state will change
+ assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+ }
+
+ @Test
+ public void testDelayExitPulsingScreenState_whenUdfpsFingerDown() {
+ // GIVEN we're pulsing
+ when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+ mHandlerFake.setMode(QUEUEING);
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ mScreen.transitionTo(DOZE_AOD, DOZE_REQUEST_PULSE);
+ mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
+ mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // WHEN udfps is activated while are transitioning back to DOZE_AOD
+ mScreen.transitionTo(DOZE_PULSE_DONE, DOZE_AOD);
+ when(mUdfpsController.isFingerDown()).thenReturn(true);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // THEN the display screen state doesn't immediately change
+ assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+ // WHEN udfpsController finger is no longer down and the queued messages are run
+ when(mUdfpsController.isFingerDown()).thenReturn(false);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // THEN the display screen state will change
+ assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 0c94f09..5c4c27c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -88,7 +88,7 @@
private FakeSettings mFakeSettings = new FakeSettings();
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
- private DozeSensors mDozeSensors;
+ private TestableDozeSensors mDozeSensors;
private TriggerSensor mSensorTap;
@Before
@@ -170,6 +170,94 @@
assertTrue(mSensorTap.mRequested);
}
+ @Test
+ public void testDozeSensorSetListening() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled */ true,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN we want to listen for the trigger sensor
+ triggerSensor.setListening(true);
+
+ // THEN the sensor is registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testDozeSensorSettingDisabled() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ false,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN setListening is called
+ triggerSensor.setListening(true);
+
+ // THEN the sensor is not registered
+ assertFalse(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testDozeSensorIgnoreSetting() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor that's
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ false,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // GIVEN sensor is listening
+ triggerSensor.setListening(true);
+
+ // WHEN ignoreSetting is called
+ triggerSensor.ignoreSetting(true);
+
+ // THEN the sensor is registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testUpdateListeningAfterAlreadyRegistered() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ true,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN setListening is called AND updateListening is called
+ triggerSensor.setListening(true);
+ triggerSensor.updateListening();
+
+ // THEN the sensor is still registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
private class TestableDozeSensors extends DozeSensors {
TestableDozeSensors() {
@@ -187,5 +275,17 @@
}
mSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
}
+
+ public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
+ boolean requiresTouchScreen) {
+ return new TriggerSensor(/* sensor */ sensor,
+ /* setting name */ "test_setting",
+ /* settingDefault */ settingEnabled,
+ /* configured */ true,
+ /* pulseReason*/ 0,
+ /* reportsTouchCoordinate*/ false,
+ requiresTouchScreen,
+ mDozeLog);
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 10997fa..9577c7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -83,6 +84,8 @@
private AuthController mAuthController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
@@ -114,7 +117,7 @@
mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
- mAuthController, mExecutor, mUiEventLogger);
+ mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
@@ -217,6 +220,32 @@
}
@Test
+ public void testPickupGesture() {
+ // GIVEN device is in doze (screen blank, but running doze sensors)
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN the pick up gesture is triggered and keyguard isn't occluded
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
+
+ // THEN wakeup
+ verify(mMachine).wakeUp();
+ }
+
+ @Test
+ public void testPickupGestureDroppedKeyguardOccluded() {
+ // GIVEN device is in doze (screen blank, but running doze sensors)
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN the pick up gesture is triggered and keyguard IS occluded
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
+
+ // THEN never wakeup
+ verify(mMachine, never()).wakeUp();
+ }
+
+ @Test
public void testOnSensor_Fingerprint() {
// GIVEN dozing state
when(mMachine.getState()).thenReturn(DOZE_AOD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 578c2d9..509ef82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -51,6 +52,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -112,6 +114,7 @@
@Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
@Mock private StatusBar mStatusBar;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private TestableLooper mTestableLooper;
@@ -156,7 +159,8 @@
mSysUiState,
mHandler,
mPackageManager,
- mStatusBar
+ mStatusBar,
+ mKeyguardUpdateMonitor
);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
@@ -422,4 +426,31 @@
restartAction.onLongPress();
verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
}
+
+ @Test
+ public void testOnLockScreen_disableSmartLock() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ doReturn(false).when(mStatusBar).isKeyguardShowing();
+ String[] actions = {
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ // When entering power menu from lockscreen, with smart lock enabled
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ mGlobalActionsDialogLite.showOrHideDialog(true, true);
+
+ // Then smart lock will be disabled
+ verify(mLockPatternUtils).requireCredentialEntry(eq(user));
+
+ // hide dialog again
+ mGlobalActionsDialogLite.showOrHideDialog(true, true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 2fa67cc..338bb30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -56,6 +56,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -126,6 +127,7 @@
@Mock private PackageManager mPackageManager;
@Mock private SecureSettings mSecureSettings;
@Mock private StatusBar mStatusBar;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private TestableLooper mTestableLooper;
@@ -169,7 +171,8 @@
mSysUiState,
mHandler,
mPackageManager,
- mStatusBar
+ mStatusBar,
+ mKeyguardUpdateMonitor
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
new file mode 100644
index 0000000..df11284
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.AnimatableClockController;
+import com.android.keyguard.AnimatableClockView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.Utils;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AnimatableClockControllerTest extends SysuiTestCase {
+ @Mock
+ private AnimatableClockView mClockView;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private BatteryController mBatteryController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private KeyguardBypassController mBypassController;
+ @Mock
+ private Resources mResources;
+
+ private MockitoSession mStaticMockSession;
+ private AnimatableClockController mAnimatableClockController;
+
+ // Capture listeners so that they can be used to send events
+ @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+ private View.OnAttachStateChangeListener mAttachListener;
+
+ @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
+ private StatusBarStateController.StateListener mStatusBarStateCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession = mockitoSession()
+ .mockStatic(Utils.class)
+ .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
+ .startMocking();
+ when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
+
+ mAnimatableClockController = new AnimatableClockController(
+ mClockView,
+ mStatusBarStateController,
+ mBroadcastDispatcher,
+ mBatteryController,
+ mKeyguardUpdateMonitor,
+ mBypassController,
+ mResources
+ );
+ mAnimatableClockController.init();
+ captureAttachListener();
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testOnAttachedUpdatesDozeStateToTrue() {
+ // GIVEN dozing
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
+
+ // WHEN the clock view gets attached
+ mAttachListener.onViewAttachedToWindow(mClockView);
+
+ // THEN the clock controller updated its dozing state to true
+ assertTrue(mAnimatableClockController.isDozing());
+ }
+
+ @Test
+ public void testOnAttachedUpdatesDozeStateToFalse() {
+ // GIVEN not dozing
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+
+ // WHEN the clock view gets attached
+ mAttachListener.onViewAttachedToWindow(mClockView);
+
+ // THEN the clock controller updated its dozing state to false
+ assertFalse(mAnimatableClockController.isDozing());
+ }
+
+ private void captureAttachListener() {
+ verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
+ mAttachListener = mAttachCaptor.getValue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index ad08780..31d70f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -67,7 +67,7 @@
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
@SmallTest
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
@@ -126,7 +126,6 @@
mUnlockedScreenOffAnimationController,
() -> mNotificationShadeDepthController);
mViewMediator.start();
- mViewMediator.onSystemReady();
}
@Test
@@ -165,8 +164,10 @@
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway() {
// When showing and provisioned
+ mViewMediator.onSystemReady();
when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
mViewMediator.setShowingLocked(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
new file mode 100644
index 0000000..9356c54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -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.keyguard;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.anyInt;
+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.util.Pair;
+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 com.airbnb.lottie.LottieAnimationView;
+
+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 @Mock LottieAnimationView mAodFp;
+
+ 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);
+ when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
+
+ 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-attached
+ Pair<Integer, PointF> udfps = setupUdfps();
+
+ // WHEN lock icon view controller is initialized and attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lock icon view location is updated with the same coordinates as fpProps
+ verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
+ assertEquals(udfps.second, mPointCaptor.getValue());
+ }
+
+ @Test
+ public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ when(mAuthController.getUdfpsProps()).thenReturn(null);
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // GIVEN fp sensor location is available post-atttached
+ captureAuthControllerCallback();
+ Pair<Integer, PointF> udfps = setupUdfps();
+
+ // WHEN all authenticators are registered
+ mAuthControllerCallback.onAllAuthenticatorsRegistered();
+
+ // THEN lock icon view location is updated with the same coordinates as fpProps
+ verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
+ assertEquals(udfps.second, mPointCaptor.getValue());
+ }
+
+ @Test
+ public void testLockIconViewBackgroundEnabledWhenUdfpsIsAvailable() {
+ // GIVEN Udpfs sensor location is available
+ setupUdfps();
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN the lock icon view background should be enabled
+ verify(mLockIconView).setUseBackground(true);
+ }
+
+ @Test
+ public void testLockIconViewBackgroundDisabledWhenUdfpsIsUnavailable() {
+ // GIVEN Udfps sensor location is not available
+ when(mAuthController.getUdfpsSensorLocation()).thenReturn(null);
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN the lock icon view background should be disabled
+ verify(mLockIconView).setUseBackground(false);
+ }
+
+ private Pair<Integer, PointF> setupUdfps() {
+ final PointF udfpsLocation = new PointF(50, 75);
+ final int radius = 33;
+ final FingerprintSensorPropertiesInternal fpProps =
+ 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));
+
+ return new Pair(radius, udfpsLocation);
+ }
+
+ 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/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index b129fdd..42629f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -37,6 +37,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
@@ -101,7 +102,6 @@
private lateinit var seamless: ViewGroup
private lateinit var seamlessIcon: ImageView
private lateinit var seamlessText: TextView
- private lateinit var seamlessFallback: ImageView
private lateinit var seekBar: SeekBar
private lateinit var elapsedTimeView: TextView
private lateinit var totalTimeView: TextView
@@ -154,8 +154,6 @@
whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
seamlessText = TextView(context)
whenever(holder.seamlessText).thenReturn(seamlessText)
- seamlessFallback = ImageView(context)
- whenever(holder.seamlessFallback).thenReturn(seamlessFallback)
seekBar = SeekBar(context)
whenever(holder.seekBar).thenReturn(seekBar)
elapsedTimeView = TextView(context)
@@ -239,21 +237,19 @@
@Test
fun bindDisabledDevice() {
seamless.id = 1
- seamlessFallback.id = 2
+ val fallbackString = context.getString(R.string.media_seamless_other_device)
player.attachPlayer(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
player.bindPlayer(state, PACKAGE)
- verify(expandedSet).setVisibility(seamless.id, View.GONE)
- verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
- verify(collapsedSet).setVisibility(seamless.id, View.GONE)
- verify(collapsedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
+ assertThat(seamless.isEnabled()).isFalse()
+ assertThat(seamlessText.getText()).isEqualTo(fallbackString)
+ assertThat(seamless.contentDescription).isEqualTo(fallbackString)
}
@Test
fun bindNullDevice() {
- val fallbackString = context.getResources().getString(
- com.android.internal.R.string.ext_media_seamless_action)
+ val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
player.attachPlayer(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index d879186..28aed20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -86,7 +86,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- mediaDataFilter = MediaDataFilter(broadcastDispatcher, mediaResumeListener,
+ mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener,
lockscreenUserManager, executor, clock)
mediaDataFilter.mediaDataManager = mediaDataManager
mediaDataFilter.addListener(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index ba6dfd3..47c5545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -4,6 +4,7 @@
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceTarget
+import android.content.Intent
import android.graphics.Bitmap
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -51,6 +52,7 @@
private const val SESSION_ARTIST = "artist"
private const val SESSION_TITLE = "title"
private const val USER_ID = 0
+private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -83,6 +85,7 @@
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
+ @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
lateinit var mediaDataManager: MediaDataManager
lateinit var mediaNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -146,8 +149,12 @@
// treat mediaSessionBasedFilter as a listener for testing.
listener = mediaSessionBasedFilter
- val recommendationExtras = Bundle()
- recommendationExtras.putString("package_name", PACKAGE_NAME)
+ val recommendationExtras = Bundle().apply {
+ putString("package_name", PACKAGE_NAME)
+ putParcelable("dismiss_intent", DISMISS_INTENT)
+ }
+ whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
+ whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras)
whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
@@ -163,7 +170,7 @@
}
@Test
- fun testSetTimedOut_deactivatesMedia() {
+ fun testSetTimedOut_active_deactivatesMedia() {
val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
@@ -176,6 +183,25 @@
}
@Test
+ fun testSetTimedOut_resume_dismissesMedia() {
+ // WHEN resume controls are present, and time out
+ val desc = MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
+ APP_NAME, pendingIntent, PACKAGE_NAME)
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
+
+ // THEN it is removed and listeners are informed
+ foregroundExecutor.advanceClockToLast()
+ foregroundExecutor.runAllReady()
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ }
+
+ @Test
fun testLoadsMetadataOnBackground() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.numPending()).isEqualTo(1)
@@ -361,7 +387,8 @@
verify(listener).onSmartspaceMediaDataLoaded(
eq(KEY_MEDIA_SMARTSPACE),
eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */,
- PACKAGE_NAME, null, listOf(mediaRecommendationItem), 0)),
+ PACKAGE_NAME, mediaSmartspaceBaseAction, listOf(mediaRecommendationItem),
+ DISMISS_INTENT, 0)),
eq(false))
}
@@ -372,7 +399,8 @@
verify(listener).onSmartspaceMediaDataLoaded(
eq(KEY_MEDIA_SMARTSPACE),
eq(EMPTY_SMARTSPACE_MEDIA_DATA
- .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, isValid = false)),
+ .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true,
+ isValid = false, dismissIntent = DISMISS_INTENT)),
eq(false))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 150f4545..359746b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -91,10 +91,12 @@
@Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
@Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
+ @Captor lateinit var componentCaptor: ArgumentCaptor<String>
private lateinit var executor: FakeExecutor
private lateinit var data: MediaData
private lateinit var resumeListener: MediaResumeListener
+ private val clock = FakeSystemClock()
private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
@@ -122,9 +124,9 @@
whenever(mockContext.packageManager).thenReturn(context.packageManager)
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
- executor = FakeExecutor(FakeSystemClock())
+ executor = FakeExecutor(clock)
resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
- tunerService, resumeBrowserFactory, dumpManager)
+ tunerService, resumeBrowserFactory, dumpManager, clock)
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
@@ -163,7 +165,7 @@
// When listener is created, we do NOT register a user change listener
val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
- resumeBrowserFactory, dumpManager)
+ resumeBrowserFactory, dumpManager, clock)
listener.setManager(mediaDataManager)
verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
any(), any(), any())
@@ -328,4 +330,109 @@
// Then we call restart
verify(resumeBrowser).restart()
}
+
+ @Test
+ fun testOnUserUnlock_missingTime_saves() {
+ val currentTime = clock.currentTimeMillis()
+
+ // When resume components without a last played time are loaded
+ testOnUserUnlock_loadsTracks()
+
+ // Then we save an update with the current time
+ verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
+ componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
+ ?.dropLastWhile { it.isEmpty() }.forEach {
+ val result = it.split("/")
+ assertThat(result.size).isEqualTo(3)
+ assertThat(result[2].toLong()).isEqualTo(currentTime)
+ }
+ verify(sharedPrefsEditor, times(1)).apply()
+ }
+
+ @Test
+ fun testLoadComponents_recentlyPlayed_adds() {
+ // Set up browser to return successfully
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ whenever(resumeBrowser.token).thenReturn(token)
+ whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+ whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+ }
+
+ // Set up shared preferences to have a component with a recent lastplayed time
+ val lastPlayed = clock.currentTimeMillis()
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When we load a component that was played recently
+ val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+ resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+
+ // We add its resume controls
+ verify(resumeBrowser, times(1)).findRecentMedia()
+ verify(mediaDataManager, times(1)).addResumptionControls(anyInt(),
+ any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+ }
+
+ @Test
+ fun testLoadComponents_old_ignores() {
+ // Set up shared preferences to have a component with an old lastplayed time
+ val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When we load a component that is not recent
+ val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+ resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+
+ // We do not try to add resume controls
+ verify(resumeBrowser, times(0)).findRecentMedia()
+ verify(mediaDataManager, times(0)).addResumptionControls(anyInt(),
+ any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testOnLoad_hasService_updatesLastPlayed() {
+ // Set up browser to return successfully
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ whenever(resumeBrowser.token).thenReturn(token)
+ whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+ whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+ }
+
+ // Set up shared preferences to have a component with a lastplayed time
+ val currentTime = clock.currentTimeMillis()
+ val lastPlayed = currentTime - 1000
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When media data is loaded that has not been checked yet, and does have a MBS
+ val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
+ resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+
+ // Then we store the new lastPlayed time
+ verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
+ componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
+ ?.dropLastWhile { it.isEmpty() }.forEach {
+ val result = it.split("/")
+ assertThat(result.size).isEqualTo(3)
+ assertThat(result[2].toLong()).isEqualTo(currentTime)
+ }
+ verify(sharedPrefsEditor, times(1)).apply()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 0a573cd6..de2235d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -71,6 +71,7 @@
private lateinit var playbackBuilder: PlaybackState.Builder
private lateinit var session: MediaSession
private lateinit var mediaData: MediaData
+ private lateinit var resumeData: MediaData
private lateinit var mediaTimeoutListener: MediaTimeoutListener
@Before
@@ -97,6 +98,10 @@
mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
device = null, active = true, resumeAction = null)
+
+ resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+ emptyList(), emptyList(), PACKAGE, null, clickIntent = null,
+ device = null, active = false, resumeAction = null, resumption = true)
}
@Test
@@ -120,6 +125,7 @@
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
assertThat(executor.numPending()).isEqualTo(1)
verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
}
@Test
@@ -188,6 +194,7 @@
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
.setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
assertThat(executor.numPending()).isEqualTo(1)
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
}
@Test
@@ -229,7 +236,7 @@
}
@Test
- fun testOnSessionDestroyed_clearsTimeout() {
+ fun testOnSessionDestroyed_active_clearsTimeout() {
// GIVEN media that is paused
val mediaPaused = mediaData.copy(isPlaying = false)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPaused)
@@ -247,7 +254,7 @@
@Test
fun testSessionDestroyed_thenRestarts_resetsTimeout() {
// Assuming we have previously destroyed the session
- testOnSessionDestroyed_clearsTimeout()
+ testOnSessionDestroyed_active_clearsTimeout()
// WHEN we get an update with media playing
val playingState = mock(android.media.session.PlaybackState::class.java)
@@ -264,4 +271,91 @@
}
verify(timeoutCallback).invoke(eq(KEY), eq(false))
}
+
+ @Test
+ fun testOnSessionDestroyed_resume_continuesTimeout() {
+ // GIVEN resume media with session info
+ val resumeWithSession = resumeData.copy(token = session.sessionToken)
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeWithSession)
+ verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // WHEN the session is destroyed
+ mediaCallbackCaptor.value.onSessionDestroyed()
+
+ // THEN the controller is unregistered, but the timeout is still scheduled
+ verify(mediaController).unregisterCallback(anyObject())
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_activeToResume_registersTimeout() {
+ // WHEN a regular media is loaded
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+
+ // AND it turns into a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData)
+
+ // THEN we register a timeout
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() {
+ // WHEN regular media is paused
+ val pausedState = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
+ .build()
+ `when`(mediaController.playbackState).thenReturn(pausedState)
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // AND it turns into a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData)
+
+ // THEN we update the timeout length
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_resumption_registersTimeout() {
+ // WHEN a resume media is loaded
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
+
+ // THEN we register a timeout
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_resumeToActive_updatesTimeout() {
+ // WHEN we have a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
+
+ // AND that media is resumed
+ val playingState = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
+ .build()
+ `when`(mediaController.playbackState).thenReturn(playingState)
+ mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
+
+ // THEN the timeout length is changed to a regular media control
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataRemoved_resume_timeoutCancelled() {
+ // WHEN we have a resume control
+ testOnMediaDataLoaded_resumption_registersTimeout()
+ // AND the media is removed
+ mediaTimeoutListener.onMediaDataRemoved(PACKAGE)
+
+ // THEN the timeout runnable is cancelled
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 03248f7..511848d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -22,11 +22,13 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.UserInfo
+import android.os.Process.SYSTEM_UID
import android.os.UserHandle
import android.permission.PermGroupUsage
import android.permission.PermissionManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
import com.android.systemui.plugins.ActivityStarter
@@ -53,6 +55,7 @@
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -96,6 +99,8 @@
private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
@Captor
private lateinit var intentCaptor: ArgumentCaptor<Intent>
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
private val backgroundExecutor = FakeExecutor(FakeSystemClock())
private val uiExecutor = FakeExecutor(FakeSystemClock())
@@ -136,6 +141,7 @@
privacyLogger,
keyguardStateController,
appOpsController,
+ uiEventLogger,
dialogProvider
)
}
@@ -550,6 +556,49 @@
verify(dialog, never()).dismiss()
}
+ @Test
+ fun testCallOnSecondaryUser() {
+ // Calls happen in
+ val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true)
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ `when`(userTracker.userProfiles).thenReturn(listOf(
+ UserInfo(ENT_USER_ID, "", 0)
+ ))
+
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ verify(dialog).show()
+ }
+
+ @Test
+ fun testStartActivityLogs() {
+ val usage = createMockPermGroupUsage()
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID)
+ verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+ USER_ID, TEST_PACKAGE_NAME)
+ }
+
+ @Test
+ fun testDismissedDialogLogs() {
+ val usage = createMockPermGroupUsage()
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor))
+
+ dialogDismissedCaptor.value.onDialogDismissed()
+
+ controller.dismissDialog()
+
+ verify(uiEventLogger, times(1)).log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+ }
+
private fun exhaustExecutors() {
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
index de7abf8..922c6b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
@@ -14,13 +14,19 @@
package com.android.systemui.qs;
-import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION;
import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.testing.AndroidTestingRunner;
@@ -28,13 +34,24 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -43,42 +60,38 @@
private static final int USER = 0;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private QSHost mQSHost;
+ @Mock
+ private DumpManager mDumpManager;
+ @Captor
+ private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor;
+
+ private Executor mBackgroundExecutor = Runnable::run; // Direct executor
private AutoAddTracker mAutoTracker;
+ private SecureSettings mSecureSettings;
@Before
public void setUp() {
- Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, "");
- }
+ MockitoAnnotations.initMocks(this);
- @Test
- public void testMigration() {
- Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
- Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true);
- mAutoTracker = new AutoAddTracker(mContext, USER);
+ mSecureSettings = new FakeSettings();
+
+ mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER);
+
+ mAutoTracker = createAutoAddTracker(USER);
mAutoTracker.initialize();
-
- assertTrue(mAutoTracker.isAdded(SAVER));
- assertTrue(mAutoTracker.isAdded(WORK));
- assertFalse(mAutoTracker.isAdded(INVERSION));
-
- // These keys have been removed; retrieving their values should always return the default.
- assertTrue(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true ));
- assertFalse(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, false));
- assertTrue(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, true));
- assertFalse(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, false));
-
- mAutoTracker.destroy();
}
@Test
public void testChangeFromBackup() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
- Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, SAVER);
- mAutoTracker.mObserver.onChange(false);
+ mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER);
assertTrue(mAutoTracker.isAdded(SAVER));
@@ -87,9 +100,6 @@
@Test
public void testSetAdded() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
mAutoTracker.setTileAdded(SAVER);
@@ -100,14 +110,12 @@
@Test
public void testPersist() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
mAutoTracker.setTileAdded(SAVER);
mAutoTracker.destroy();
- mAutoTracker = new AutoAddTracker(mContext, USER);
+ mAutoTracker = createAutoAddTracker(USER);
+ mAutoTracker.initialize();
assertTrue(mAutoTracker.isAdded(SAVER));
@@ -116,22 +124,158 @@
@Test
public void testIndependentUsers() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
mAutoTracker.setTileAdded(SAVER);
- mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+ mAutoTracker = createAutoAddTracker(USER + 1);
+ mAutoTracker.initialize();
assertFalse(mAutoTracker.isAdded(SAVER));
}
@Test
public void testChangeUser() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
mAutoTracker.setTileAdded(SAVER);
- mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+ mAutoTracker = createAutoAddTracker(USER + 1);
mAutoTracker.changeUser(UserHandle.of(USER));
assertTrue(mAutoTracker.isAdded(SAVER));
}
+
+ @Test
+ public void testBroadcastReceiverRegistered() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)));
+
+ assertTrue(
+ mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
+ }
+
+ @Test
+ public void testBroadcastReceiverChangesWithUser() {
+ mAutoTracker.changeUser(UserHandle.of(USER + 1));
+
+ InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
+ inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
+ inOrder.verify(mBroadcastDispatcher)
+ .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,work,internet,cast";
+ Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, don't remove any current tiles
+ verify(mQSHost, never()).removeTiles(any());
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent =
+ makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent =
+ makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+
+ String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER);
+ assertEquals(2, setting.split(",").length);
+ assertTrue(setting.contains("work"));
+ assertTrue(setting.contains("inversion"));
+ }
+
+
+ private Intent makeRestoreIntent(
+ String settingName, String previousValue, String restoredValue) {
+ Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED);
+ intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName);
+ intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue);
+ intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue);
+ return intent;
+ }
+
+ private AutoAddTracker createAutoAddTracker(int user) {
+ // Null handler wil dispatch sync.
+ return new AutoAddTracker(
+ mSecureSettings,
+ mBroadcastDispatcher,
+ mQSHost,
+ mDumpManager,
+ null,
+ mBackgroundExecutor,
+ user
+ );
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65e5f97..faef870 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -98,11 +99,11 @@
Resources mResources;
@Mock
Configuration mConfiguration;
+ @Mock
+ Runnable mHorizontalLayoutListener;
private QSPanelControllerBase<QSPanel> mController;
-
-
/** Implementation needed to ensure we have a reflectively-available class name. */
private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
@@ -242,18 +243,44 @@
when(mMediaHost.getVisible()).thenReturn(true);
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isFalse();
}
+
+ @Test
+ public void testChangeConfiguration_shouldUseHorizontalLayout() {
+ when(mMediaHost.getVisible()).thenReturn(true);
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+
+ // When device is rotated to landscape
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ verify(mHorizontalLayoutListener).run(); // not invoked
+
+ // When it is rotated back to portrait
+ mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes back
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener, times(2)).run();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index bf6c981..35ebacb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -23,6 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -98,6 +100,10 @@
FalsingManagerFake mFalsingManager = new FalsingManagerFake();
@Mock
FeatureFlags mFeatureFlags;
+ @Mock
+ Resources mResources;
+ @Mock
+ Configuration mConfiguration;
private QSPanelController mController;
@@ -109,6 +115,8 @@
when(mQSPanel.getDumpableTag()).thenReturn("QSPanel");
when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout);
when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
+ when(mQSPanel.getResources()).thenReturn(mResources);
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
when(mToggleSliderViewControllerFactory.create(any(), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 4cbad5f..0b67c9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -18,14 +18,11 @@
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.TestCase.assertFalse;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -68,12 +65,12 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -121,7 +118,6 @@
private UiEventLogger mUiEventLogger;
@Mock
private UserTracker mUserTracker;
- @Mock
private SecureSettings mSecureSettings;
@Mock
private CustomTileStatePersister mCustomTileStatePersister;
@@ -137,14 +133,16 @@
MockitoAnnotations.initMocks(this);
mLooper = TestableLooper.get(this);
mHandler = new Handler(mLooper.getLooper());
+
+ mSecureSettings = new FakeSettings();
+ mSecureSettings.putStringForUser(
+ QSTileHost.TILES_SETTING, "", "", false, mUserTracker.getUserId(), false);
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
mSecureSettings, mCustomTileStatePersister, mFeatureFlags);
setUpTileFactory();
when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
- when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt()))
- .thenReturn("");
}
private void setUpTileFactory() {
@@ -416,6 +414,16 @@
.removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
}
+ @Test
+ public void testRemoveTiles() {
+ List<String> tiles = List.of("spec1", "spec2", "spec3");
+ mQSTileHost.saveTilesToSettings(tiles);
+
+ mQSTileHost.removeTiles(List.of("spec1", "spec2"));
+
+ assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
+ }
+
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
QSFactory defaultFactory, Handler mainHandler, Looper bgLooper,
@@ -442,14 +450,11 @@
@Override
void saveTilesToSettings(List<String> tileSpecs) {
super.saveTilesToSettings(tileSpecs);
-
- ArgumentCaptor<String> specs = ArgumentCaptor.forClass(String.class);
- verify(mSecureSettings, atLeastOnce()).putStringForUser(eq(QSTileHost.TILES_SETTING),
- specs.capture(), isNull(), eq(false), anyInt(), eq(true));
-
// After tiles are changed, make sure to call onTuningChanged with the new setting if it
// changed
- onTuningChanged(TILES_SETTING, specs.getValue());
+ String specs = mSecureSettings.getStringForUser(
+ QSTileHost.TILES_SETTING, mUserTracker.getUserId());
+ onTuningChanged(TILES_SETTING, specs);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 35360bd..8b7e20e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -36,6 +36,8 @@
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.Clock
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -87,8 +89,14 @@
@Mock
private lateinit var privacyDialogController: PrivacyDialogController
@Mock
+ private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+ @Mock
+ private lateinit var variableDateViewController: VariableDateViewController
+ @Mock
private lateinit var clock: Clock
@Mock
+ private lateinit var variableDateView: VariableDateView
+ @Mock
private lateinit var mockView: View
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var context: Context
@@ -109,6 +117,8 @@
stubViews()
`when`(iconContainer.context).thenReturn(context)
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+ `when`(variableDateViewControllerFactory.create(any()))
+ .thenReturn(variableDateViewController)
`when`(view.resources).thenReturn(mContext.resources)
`when`(view.isAttachedToWindow).thenReturn(true)
`when`(view.context).thenReturn(context)
@@ -133,7 +143,8 @@
colorExtractor,
privacyDialogController,
qsExpansionPathInterpolator,
- featureFlags
+ featureFlags,
+ variableDateViewControllerFactory
)
}
@@ -274,6 +285,8 @@
`when`(view.findViewById<StatusIconContainer>(R.id.statusIcons)).thenReturn(iconContainer)
`when`(view.findViewById<OngoingPrivacyChip>(R.id.privacy_chip)).thenReturn(privacyChip)
`when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock)
+ `when`(view.requireViewById<VariableDateView>(R.id.date)).thenReturn(variableDateView)
+ `when`(view.requireViewById<VariableDateView>(R.id.date_clock)).thenReturn(variableDateView)
}
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 63ebe92..23e5168 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -74,6 +75,24 @@
}
@Test
+ public void testMutateIconDrawable() {
+ SlashImageView iv = mock(SlashImageView.class);
+ Drawable originalDrawable = mock(Drawable.class);
+ Drawable otherDrawable = mock(Drawable.class);
+ State s = new State();
+ s.icon = mock(Icon.class);
+ when(s.icon.getInvisibleDrawable(eq(mContext))).thenReturn(originalDrawable);
+ when(s.icon.getDrawable(eq(mContext))).thenReturn(originalDrawable);
+ when(iv.isShown()).thenReturn(true);
+ when(originalDrawable.getConstantState()).thenReturn(fakeConstantState(otherDrawable));
+
+
+ mIconView.updateIcon(iv, s, /* allowAnimations= */true);
+
+ verify(iv).setState(any(), eq(otherDrawable));
+ }
+
+ @Test
public void testNoFirstFade() {
ImageView iv = mock(ImageView.class);
State s = new State();
@@ -104,4 +123,18 @@
public void testIconNotSet_toString() {
assertFalse(mIconView.toString().contains("lastIcon"));
}
+
+ private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
+ return new Drawable.ConstantState() {
+ @Override
+ public Drawable newDrawable() {
+ return otherDrawable;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return 1;
+ }
+ };
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
index 5e2d8fd..b4a66297 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
@@ -3,6 +3,7 @@
import android.app.AlarmManager
import android.app.PendingIntent
import android.os.Handler
+import android.provider.AlarmClock
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -98,6 +99,11 @@
}
@Test
+ fun testDefaultIntentShowAlarms() {
+ assertThat(tile.defaultIntent.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+ }
+
+ @Test
fun testInactiveByDefault() {
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index d44a526..e939411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -17,6 +17,7 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -327,4 +328,77 @@
assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name));
}
+
+ @Test
+ public void testExpandView_wifiNotConnected() {
+ mCastTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_wifiEnabledNotCasting() {
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_casting_projection() {
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastController.CastDevice.STATE_CONNECTED;
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_connecting_projection() {
+ CastController.CastDevice connecting = new CastController.CastDevice();
+ connecting.state = CastDevice.STATE_CONNECTING;
+ connecting.name = "Test Casting Device";
+
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(connecting);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_casting_mediaRoute() {
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastDevice.STATE_CONNECTED;
+ device.tag = mock(MediaRouter.RouteInfo.class);
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_connecting_mediaRoute() {
+ CastController.CastDevice connecting = new CastController.CastDevice();
+ connecting.state = CastDevice.STATE_CONNECTING;
+ connecting.tag = mock(RouteInfo.class);
+ connecting.name = "Test Casting Device";
+
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(connecting);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
new file mode 100644
index 0000000..49ab777
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
+
+import static junit.framework.TestCase.assertEquals;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.text.TextUtils;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class RotationLockTileTest extends SysuiTestCase {
+
+ private static final String PACKAGE_NAME = "package_name";
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private QSTileHost mHost;
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private QSLogger mQSLogger;
+ @Mock
+ private SensorPrivacyManager mPrivacyManager;
+ @Mock
+ private BatteryController mBatteryController;
+
+ private SecureSettings mSecureSettings;
+ private RotationLockController mController;
+ private TestableLooper mTestableLooper;
+ private RotationLockTile mLockTile;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTestableLooper = TestableLooper.get(this);
+
+ when(mHost.getContext()).thenReturn(mContext);
+ when(mHost.getUserContext()).thenReturn(mContext);
+
+ mSecureSettings = new FakeSettings();
+ mController = new RotationLockControllerImpl(mContext, mSecureSettings);
+
+ mLockTile = new RotationLockTile(
+ mHost,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mController,
+ mPrivacyManager,
+ mBatteryController,
+ mSecureSettings
+ );
+
+ mLockTile.initialize();
+
+ // We are not setting the mocks to listening, so we trigger a first refresh state to
+ // set the initial state
+ mLockTile.refreshState();
+
+ mTestableLooper.processAllMessages();
+
+ mContext.setMockPackageManager(mPackageManager);
+ doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName();
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+ Manifest.permission.CAMERA, PACKAGE_NAME);
+
+ when(mBatteryController.isPowerSave()).thenReturn(false);
+ when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(false);
+ enableAutoRotation();
+ enableCameraBasedRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ public void testSecondaryString_cameraRotateOn_returnsFaceBased() {
+ assertEquals("On - Face-based", mLockTile.getState().secondaryLabel);
+ }
+
+ @Test
+ public void testSecondaryString_rotateOff_isEmpty() {
+ disableAutoRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_cameraRotateOff_isEmpty() {
+ disableCameraBasedRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_powerSaveEnabled_isEmpty() {
+ when(mBatteryController.isPowerSave()).thenReturn(true);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_cameraDisabled_isEmpty() {
+ when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(true);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_noCameraPermission_isEmpty() {
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ Manifest.permission.CAMERA, PACKAGE_NAME);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ private void enableAutoRotation() {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 1, UserHandle.USER_CURRENT);
+ }
+
+ private void disableAutoRotation() {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT);
+ }
+
+ private void enableCameraBasedRotation() {
+ mSecureSettings.putIntForUser(
+ CAMERA_AUTOROTATE, 1, UserHandle.USER_CURRENT);
+ }
+
+ private void disableCameraBasedRotation() {
+ mSecureSettings.putIntForUser(
+ CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
new file mode 100644
index 0000000..77946cf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
@@ -0,0 +1,167 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class InternetAdapterTest extends SysuiTestCase {
+
+ private static final String WIFI_TITLE = "Wi-Fi Title";
+ private static final String WIFI_SUMMARY = "Wi-Fi Summary";
+ private static final int GEAR_ICON_RES_ID = R.drawable.ic_settings_24dp;
+ private static final int LOCK_ICON_RES_ID = R.drawable.ic_friction_lock_closed;
+
+ @Rule
+ public MockitoRule mRule = MockitoJUnit.rule();
+
+ @Mock
+ private WifiEntry mInternetWifiEntry;
+ @Mock
+ private List<WifiEntry> mWifiEntries;
+ @Mock
+ private WifiEntry mWifiEntry;
+ @Mock
+ private InternetDialogController mInternetDialogController;
+ @Mock
+ private WifiUtils.InternetIconInjector mWifiIconInjector;
+ @Mock
+ private Drawable mGearIcon;
+ @Mock
+ private Drawable mLockIcon;
+
+ private TestableResources mTestableResources;
+ private InternetAdapter mInternetAdapter;
+ private InternetAdapter.InternetViewHolder mViewHolder;
+
+ @Before
+ public void setUp() {
+ mTestableResources = mContext.getOrCreateTestableResources();
+ when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+ when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
+ when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+
+ mInternetAdapter = new InternetAdapter(mInternetDialogController);
+ mViewHolder = mInternetAdapter.onCreateViewHolder(new LinearLayout(mContext), 0);
+ mInternetAdapter.setWifiEntries(Arrays.asList(mWifiEntry), 1 /* wifiEntriesCount */);
+ mViewHolder.mWifiIconInjector = mWifiIconInjector;
+ }
+
+ @Test
+ public void getItemCount_returnWifiEntriesCount() {
+ for (int i = 0; i < InternetDialogController.MAX_WIFI_ENTRY_COUNT; i++) {
+ mInternetAdapter.setWifiEntries(mWifiEntries, i /* wifiEntriesCount */);
+
+ assertThat(mInternetAdapter.getItemCount()).isEqualTo(i);
+ }
+ }
+
+ @Test
+ public void onBindViewHolder_bindWithOpenWifiNetwork_verifyView() {
+ when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_NONE);
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onBindViewHolder_bindWithSecurityWifiNetwork_verifyView() {
+ when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_PSK);
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void onBindViewHolder_wifiLevelUnreachable_shouldNotGetWifiIcon() {
+ reset(mWifiIconInjector);
+ when(mWifiEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector, never()).getIcon(anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void onBindViewHolder_shouldNotShowXLevelIcon_getIconWithInternet() {
+ when(mWifiEntry.shouldShowXLevelIcon()).thenReturn(false);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector).getIcon(eq(false) /* noInternet */, anyInt());
+ }
+
+ @Test
+ public void onBindViewHolder_shouldShowXLevelIcon_getIconWithNoInternet() {
+ when(mWifiEntry.shouldShowXLevelIcon()).thenReturn(true);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector).getIcon(eq(true) /* noInternet */, anyInt());
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiConnected_updateGearIcon() {
+ mTestableResources.addOverride(GEAR_ICON_RES_ID, mGearIcon);
+
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_CONNECTED, WifiEntry.SECURITY_PSK);
+
+ assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mGearIcon);
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityPsk_updateLockIcon() {
+ mTestableResources.addOverride(LOCK_ICON_RES_ID, mLockIcon);
+
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_PSK);
+
+ assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mLockIcon);
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityNone_hideIcon() {
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_NONE);
+
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
new file mode 100644
index 0000000..f9d5be6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -0,0 +1,646 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
+import com.android.systemui.toast.SystemUIToast;
+import com.android.systemui.toast.ToastFactory;
+import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.wifitrackerlib.MergedCarrierEntry;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class InternetDialogControllerTest extends SysuiTestCase {
+
+ private static final int SUB_ID = 1;
+ private static final String CONNECTED_TITLE = "Connected Wi-Fi Title";
+ private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary";
+
+ //SystemUIToast
+ private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
+ private static final int TOAST_MESSAGE_STRING_ID = 1;
+ private static final String TOAST_MESSAGE_STRING = "toast message";
+
+ @Mock
+ private WifiManager mWifiManager;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private SubscriptionManager mSubscriptionManager;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private Handler mWorkerHandler;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private GlobalSettings mGlobalSettings;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private NetworkController.AccessPointController mAccessPointController;
+ @Mock
+ private WifiEntry mConnectedEntry;
+ @Mock
+ private WifiEntry mWifiEntry1;
+ @Mock
+ private WifiEntry mWifiEntry2;
+ @Mock
+ private WifiEntry mWifiEntry3;
+ @Mock
+ private WifiEntry mWifiEntry4;
+ @Mock
+ private MergedCarrierEntry mMergedCarrierEntry;
+ @Mock
+ private ServiceState mServiceState;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private WifiUtils.InternetIconInjector mWifiIconInjector;
+ @Mock
+ InternetDialogController.InternetDialogCallback mInternetDialogCallback;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private ToastFactory mToastFactory;
+ @Mock
+ private SystemUIToast mSystemUIToast;
+ @Mock
+ private View mToastView;
+ @Mock
+ private Animator mAnimator;
+ @Mock
+ private CarrierConfigTracker mCarrierConfigTracker;
+
+ private TestableResources mTestableResources;
+ private MockInternetDialogController mInternetDialogController;
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private List<WifiEntry> mAccessPoints = new ArrayList<>();
+ private List<WifiEntry> mWifiEntries = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTestableResources = mContext.getOrCreateTestableResources();
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+ when(mConnectedEntry.isDefaultNetwork()).thenReturn(true);
+ when(mConnectedEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry2.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry3.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry4.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
+ when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
+ .thenReturn(mSystemUIToast);
+ when(mSystemUIToast.getView()).thenReturn(mToastView);
+ when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
+ when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator);
+
+ mInternetDialogController = new MockInternetDialogController(mContext,
+ mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
+ mSubscriptionManager, mTelephonyManager, mWifiManager,
+ mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
+ mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
+ mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker);
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
+ mInternetDialogController.mOnSubscriptionsChangedListener);
+ mInternetDialogController.onStart(mInternetDialogCallback, true);
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+ mInternetDialogController.mActivityStarter = mActivityStarter;
+ mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+ }
+
+ @Test
+ public void connectCarrierNetwork_mergedCarrierEntryCanConnect_connectAndCreateSysUiToast() {
+ when(mMergedCarrierEntry.canConnect()).thenReturn(true);
+ mTestableResources.addOverride(R.string.wifi_wont_autoconnect_for_now,
+ TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.connectCarrierNetwork();
+
+ verify(mMergedCarrierEntry).connect(null /* callback */, false /* showToast */);
+ verify(mToastFactory).createToast(any(), eq(TOAST_MESSAGE_STRING), anyString(), anyInt(),
+ anyInt());
+ }
+
+ @Test
+ public void makeOverlayToast_withGravityFlags_addViewWithLayoutParams() {
+ mTestableResources.addOverride(TOAST_MESSAGE_STRING_ID, TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.makeOverlayToast(TOAST_MESSAGE_STRING_ID);
+
+ ArgumentCaptor<WindowManager.LayoutParams> paramsCaptor = ArgumentCaptor.forClass(
+ WindowManager.LayoutParams.class);
+ verify(mWindowManager).addView(eq(mToastView), paramsCaptor.capture());
+ WindowManager.LayoutParams params = paramsCaptor.getValue();
+ assertThat(params.format).isEqualTo(PixelFormat.TRANSLUCENT);
+ assertThat(params.type).isEqualTo(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ assertThat(params.horizontalWeight).isEqualTo(TOAST_PARAMS_HORIZONTAL_WEIGHT);
+ assertThat(params.verticalWeight).isEqualTo(TOAST_PARAMS_VERTICAL_WEIGHT);
+ }
+
+ @Test
+ public void makeOverlayToast_withAnimation_verifyAnimatorStart() {
+ mTestableResources.addOverride(TOAST_MESSAGE_STRING_ID, TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.makeOverlayToast(TOAST_MESSAGE_STRING_ID);
+
+ verify(mAnimator).start();
+ }
+
+ @Test
+ public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() {
+ mInternetDialogController.setAirplaneModeEnabled(true);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
+ getResourcesString("airplane_mode")));
+ }
+
+ @Test
+ public void getDialogTitleText_withAirplaneModeOff_returnInternet() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
+ getResourcesString("quick_settings_internet_label")));
+ }
+
+ @Test
+ public void getSubtitleText_withAirplaneModeOn_returnNull() {
+ mInternetDialogController.setAirplaneModeEnabled(true);
+
+ assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
+ }
+
+ @Test
+ public void getSubtitleText_withWifiOff_returnWifiIsOff() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("wifi_is_off"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("wifi_is_off"));
+ }
+
+ @Test
+ public void getSubtitleText_withNoWifiEntry_returnSearchWifi() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ assertThat(mInternetDialogController.getSubtitleText(true))
+ .isEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(true))
+ .isNotEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+ }
+
+ @Test
+ public void getSubtitleText_withWifiEntry_returnTapToConnect() {
+ // The preconditions WiFi Entries is already in setUp()
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("tap_a_network_to_connect"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("tap_a_network_to_connect"));
+ }
+
+ @Test
+ public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ getResourcesString("unlock_to_view_networks")));
+ }
+
+ @Test
+ public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+ doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+ doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState();
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+ }
+
+ @Test
+ public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
+ doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+
+ when(mTelephonyManager.isDataEnabled()).thenReturn(false);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("non_carrier_network_unavailable"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("non_carrier_network_unavailable"));
+ }
+
+ @Test
+ public void getWifiDetailsSettingsIntent_withNoKey_returnNull() {
+ assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull();
+ }
+
+ @Test
+ public void getWifiDetailsSettingsIntent_withKey_returnIntent() {
+ assertThat(mInternetDialogController.getWifiDetailsSettingsIntent("test_key")).isNotNull();
+ }
+
+ @Test
+ public void getInternetWifiDrawable_withConnectedEntry_returnIntentIconWithCorrectColor() {
+ final Drawable drawable = mock(Drawable.class);
+ when(mWifiIconInjector.getIcon(anyBoolean(), anyInt())).thenReturn(drawable);
+
+ mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+ verify(mWifiIconInjector).getIcon(eq(false), anyInt());
+ verify(drawable).setTint(mContext.getColor(R.color.connected_network_primary_color));
+ }
+
+ @Test
+ public void getInternetWifiDrawable_withWifiLevelUnreachable_returnNull() {
+ when(mConnectedEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
+
+ Drawable drawable = mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+ assertThat(drawable).isNull();
+ }
+
+ @Test
+ public void launchWifiNetworkDetailsSetting_withNoWifiEntryKey_doNothing() {
+ mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */);
+
+ verify(mActivityStarter, never())
+ .postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ }
+
+ @Test
+ public void launchWifiNetworkDetailsSetting_withWifiEntryKey_startActivity() {
+ mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key");
+
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ }
+
+ @Test
+ public void isDeviceLocked_keyguardIsUnlocked_returnFalse() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+
+ assertThat(mInternetDialogController.isDeviceLocked()).isFalse();
+ }
+
+ @Test
+ public void isDeviceLocked_keyguardIsLocked_returnTrue() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ assertThat(mInternetDialogController.isDeviceLocked()).isTrue();
+ }
+
+ @Test
+ public void onAccessPointsChanged_canNotConfigWifi_doNothing() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.mCanConfigWifi = false;
+
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any());
+ }
+
+ @Test
+ public void onAccessPointsChanged_nullAccessPoints_callbackBothNull() {
+ reset(mInternetDialogCallback);
+
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mWifiEntry1);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+ mAccessPoints.add(mWifiEntry4);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+ mAccessPoints.add(mWifiEntry4);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ mWifiEntries.add(mWifiEntry4);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+
+ // If the Ethernet exists, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.mHasEthernet = true;
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry4);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(true);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+
+ verify(mMergedCarrierEntry, never()).setEnabled(anyBoolean());
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_mergedCarrierEntryEmpty_doesntCrash() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(false);
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(null);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_neededSetMergedCarrierEntry_setTogether() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(false);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+
+ verify(mMergedCarrierEntry).setEnabled(true);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, false);
+
+ verify(mMergedCarrierEntry).setEnabled(false);
+ }
+
+ private String getResourcesString(String name) {
+ return mContext.getResources().getString(getResourcesId(name));
+ }
+
+ private int getResourcesId(String name) {
+ return mContext.getResources().getIdentifier(name, "string",
+ mContext.getPackageName());
+ }
+
+ private class MockInternetDialogController extends InternetDialogController {
+
+ private GlobalSettings mGlobalSettings;
+ private boolean mIsAirplaneModeOn;
+
+ MockInternetDialogController(Context context, UiEventLogger uiEventLogger,
+ ActivityStarter starter, AccessPointController accessPointController,
+ SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
+ @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+ @Main Handler handler, @Main Executor mainExecutor,
+ BroadcastDispatcher broadcastDispatcher,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
+ KeyguardStateController keyguardStateController, WindowManager windowManager,
+ ToastFactory toastFactory, Handler workerHandler,
+ CarrierConfigTracker carrierConfigTracker) {
+ super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
+ telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
+ broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
+ keyguardStateController, windowManager, toastFactory, workerHandler,
+ carrierConfigTracker);
+ mGlobalSettings = globalSettings;
+ }
+
+ @Override
+ boolean isAirplaneModeEnabled() {
+ return mIsAirplaneModeOn;
+ }
+
+ public void setAirplaneModeEnabled(boolean enabled) {
+ mIsAirplaneModeOn = enabled;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
new file mode 100644
index 0000000..7cea430
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -0,0 +1,381 @@
+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 android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class InternetDialogTest extends SysuiTestCase {
+
+ private static final String MOBILE_NETWORK_TITLE = "Mobile Title";
+ private static final String MOBILE_NETWORK_SUMMARY = "Mobile Summary";
+ private static final String WIFI_TITLE = "Connected Wi-Fi Title";
+ private static final String WIFI_SUMMARY = "Connected Wi-Fi Summary";
+
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private WifiManager mWifiManager;
+ @Mock
+ private WifiEntry mInternetWifiEntry;
+ @Mock
+ private List<WifiEntry> mWifiEntries;
+ @Mock
+ private InternetAdapter mInternetAdapter;
+ @Mock
+ private InternetDialogController mInternetDialogController;
+
+ private InternetDialog mInternetDialog;
+ private View mDialogView;
+ private View mSubTitle;
+ private LinearLayout mEthernet;
+ private LinearLayout mMobileDataToggle;
+ private LinearLayout mWifiToggle;
+ private LinearLayout mConnectedWifi;
+ private RecyclerView mWifiList;
+ private LinearLayout mSeeAll;
+ private LinearLayout mWifiScanNotify;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+ when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
+ when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntries.size()).thenReturn(1);
+
+ when(mInternetDialogController.getMobileNetworkTitle()).thenReturn(MOBILE_NETWORK_TITLE);
+ when(mInternetDialogController.getMobileNetworkSummary())
+ .thenReturn(MOBILE_NETWORK_SUMMARY);
+ when(mInternetDialogController.getWifiManager()).thenReturn(mWifiManager);
+
+ mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class),
+ mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler);
+ mInternetDialog.mAdapter = mInternetAdapter;
+ mInternetDialog.onAccessPointsChanged(mWifiEntries, mInternetWifiEntry);
+ mInternetDialog.show();
+
+ mDialogView = mInternetDialog.mDialogView;
+ mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+ mEthernet = mDialogView.requireViewById(R.id.ethernet_layout);
+ mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+ mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout);
+ mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout);
+ mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
+ mWifiScanNotify = mDialogView.requireViewById(R.id.wifi_scan_notify_layout);
+ }
+
+ @After
+ public void tearDown() {
+ mInternetDialog.dismissDialog();
+ }
+
+ @Test
+ public void hideWifiViews_WifiViewsGone() {
+ mInternetDialog.hideWifiViews();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOn_internetDialogSubTitleGone() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mSubTitle.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOff_internetDialogSubTitleVisible() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOffAndHasEthernet_showEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+ when(mInternetDialogController.hasEthernet()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOffAndNoEthernet_hideEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+ when(mInternetDialogController.hasEthernet()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_apmOnAndHasEthernet_showEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ when(mInternetDialogController.hasEthernet()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOnAndNoEthernet_hideEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ when(mInternetDialogController.hasEthernet()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOn_mobileDataLayoutGone() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndNoConnectedWifi_hideConnectedWifi() {
+ // The precondition WiFi ON is already in setUp()
+ mInternetDialog.onAccessPointsChanged(mWifiEntries, null /* connectedEntry*/);
+ doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndNoWifiList_hideWifiListAndSeeAll() {
+ // The precondition WiFi ON is already in setUp()
+ mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, mInternetWifiEntry);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasInternetWifi_showHighlightWifiToggle() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWifiToggle.getBackground()).isNotNull();
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasInternetWifi_hideConnectedWifi() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasWifiList_hideWifiListAndSeeAll() {
+ // The preconditions WiFi entries are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOn_hideWifiScanNotify() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOffAndWifiScanOff_hideWifiScanNotify() {
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+ when(mWifiManager.isScanAlwaysAvailable()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOffAndWifiScanOnAndDeviceLocked_hideWifiScanNotify() {
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+ when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOffAndWifiScanOnAndDeviceUnlocked_showWifiScanNotify() {
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+ when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.VISIBLE);
+ TextView wifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text);
+ assertThat(wifiScanNotifyText.getText().length()).isNotEqualTo(0);
+ assertThat(wifiScanNotifyText.getMovementMethod()).isNotNull();
+ }
+
+ @Test
+ public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
+ mSeeAll.performClick();
+
+ verify(mInternetDialogController).launchNetworkSetting();
+ }
+
+ @Test
+ public void showProgressBar_wifiDisabled_hideProgressBar() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ mInternetDialog.showProgressBar();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+ }
+
+ @Test
+ public void showProgressBar_deviceLocked_hideProgressBar() {
+ Mockito.reset(mHandler);
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.showProgressBar();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+ }
+
+ @Test
+ public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ mInternetDialog.showProgressBar();
+
+ // Show progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).postDelayed(runnableCaptor.capture(),
+ eq(InternetDialog.PROGRESS_DELAY_MS));
+ runnableCaptor.getValue().run();
+
+ // Then hide progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ }
+
+ @Test
+ public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry*/);
+
+ mInternetDialog.showProgressBar();
+
+ // Show progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).postDelayed(runnableCaptor.capture(),
+ eq(InternetDialog.PROGRESS_DELAY_MS));
+ runnableCaptor.getValue().run();
+
+ // Then hide searching sub-title only
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+ assertThat(mInternetDialog.mIsSearchingHidden).isTrue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
index 10c878a..6f081c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
@@ -34,6 +34,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -274,7 +275,8 @@
when(client.start(/* response */ any(), /* maxPages */ anyFloat()))
.thenReturn(immediateFuture(session));
return new ScrollCaptureController(context, context.getMainExecutor(),
- client, new ImageTileSet(context.getMainThreadHandler()));
+ client, new ImageTileSet(context.getMainThreadHandler()),
+ new UiEventLoggerFake());
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 21c6292..f3762c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -423,17 +423,18 @@
final boolean credentialAllowed = true;
final boolean requireConfirmation = true;
final int userId = 10;
- final String packageName = "test";
final long operationId = 1;
+ final String packageName = "test";
+ final long requestId = 10;
final int multiSensorConfig = BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
mCommandQueue.showAuthenticationDialog(promptInfo, receiver, sensorIds,
- credentialAllowed, requireConfirmation , userId, packageName, operationId,
+ credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId,
multiSensorConfig);
waitForIdleSync();
verify(mCallbacks).showAuthenticationDialog(eq(promptInfo), eq(receiver), eq(sensorIds),
- eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(packageName),
- eq(operationId), eq(multiSensorConfig));
+ eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(operationId),
+ eq(packageName), eq(requestId), eq(multiSensorConfig));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index f5ce673..f5cab1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -23,6 +23,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
@@ -111,6 +112,7 @@
private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo",
"bar");
+ private String mKeyguardTryFingerprintMsg;
private String mDisclosureWithOrganization;
private String mDisclosureGeneric;
private String mFinancedDisclosureWithOrganization;
@@ -182,6 +184,7 @@
mContext.addMockSystemService(UserManager.class, mUserManager);
mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
+ mKeyguardTryFingerprintMsg = mContext.getString(R.string.keyguard_try_fingerprint);
mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
ORGANIZATION_NAME);
mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
@@ -637,7 +640,7 @@
}
@Test
- public void onRefreshBatteryInfo_fullChargedWithOverheat_presentCharged() {
+ public void onRefreshBatteryInfo_fullChargedWithOverheat_presentChargingLimited() {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
@@ -649,6 +652,24 @@
verifyIndicationMessage(
INDICATION_TYPE_BATTERY,
+ mContext.getString(
+ R.string.keyguard_plugged_in_charging_limited,
+ NumberFormat.getPercentInstance().format(100 / 100f)));
+ }
+
+ @Test
+ public void onRefreshBatteryInfo_fullChargedWithoutOverheat_presentCharged() {
+ createController();
+ BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+ 100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
+ BatteryManager.BATTERY_HEALTH_GOOD, 0 /* maxChargingWattage */,
+ true /* present */);
+
+ mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+ mController.setVisible(true);
+
+ verifyIndicationMessage(
+ INDICATION_TYPE_BATTERY,
mContext.getString(R.string.keyguard_charged));
}
@@ -677,6 +698,48 @@
verifyTransientMessage(message);
}
+ @Test
+ public void faceAuthMessageSuppressed() {
+ createController();
+ String faceHelpMsg = "Face auth help message";
+
+ // GIVEN state of showing message when keyguard screen is on
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
+
+ // GIVEN fingerprint is also running (not udfps)
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsAvailable()).thenReturn(false);
+
+ mController.setVisible(true);
+
+ // WHEN a face help message comes in
+ mController.getKeyguardCallback().onBiometricHelp(
+ KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, faceHelpMsg,
+ BiometricSourceType.FACE);
+
+ // THEN "try fingerprint" message appears (and not the face help message)
+ verifyTransientMessage(mKeyguardTryFingerprintMsg);
+
+ // THEN the face help message is still announced for a11y
+ verify(mIndicationAreaBottom).announceForAccessibility(eq(faceHelpMsg));
+ }
+
+ @Test
+ public void testEmptyOwnerInfoHidesIndicationArea() {
+ createController();
+
+ // GIVEN the owner info is set to an empty string
+ when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn("");
+
+ // WHEN asked to update the indication area
+ mController.setVisible(true);
+
+ // THEN the owner info should be hidden
+ verifyHideIndication(INDICATION_TYPE_OWNER_INFO);
+ }
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
new file mode 100644
index 0000000..97fe25d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.Consumer
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LightRevealScrimTest : SysuiTestCase() {
+
+ private lateinit var scrim: LightRevealScrim
+ private var isOpaque = false
+
+ @Before
+ fun setUp() {
+ scrim = LightRevealScrim(context, null)
+ scrim.isScrimOpaqueChangedListener = Consumer { opaque ->
+ isOpaque = opaque
+ }
+ scrim.revealAmount = 0f
+ assertTrue("Scrim is not opaque in initial setup", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testAlphaSetsOpaque() {
+ scrim.alpha = 0.5f
+ assertFalse("Scrim is opaque even though alpha is set", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testVisibilitySetsOpaque() {
+ scrim.visibility = View.INVISIBLE
+ assertFalse("Scrim is opaque even though it's invisible", scrim.isScrimOpaque)
+ scrim.visibility = View.GONE
+ assertFalse("Scrim is opaque even though it's gone", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testRevealSetsOpaque() {
+ scrim.revealAmount = 0.5f
+ assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 7c045c1..465370b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -25,6 +25,7 @@
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
@@ -32,6 +33,7 @@
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -69,7 +71,6 @@
@Mock private lateinit var root: View
@Mock private lateinit var viewRootImpl: ViewRootImpl
@Mock private lateinit var windowToken: IBinder
- @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var listener: NotificationShadeDepthController.DepthListener
@@ -89,10 +90,10 @@
`when`(root.isAttachedToWindow).thenReturn(true)
`when`(statusBarStateController.state).then { statusBarState }
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
- (answer.arguments[0] as Float * maxBlur).toInt()
+ answer.arguments[0] as Float * maxBlur.toFloat()
}
- `when`(blurUtils.ratioOfBlurRadius(anyInt())).then { answer ->
- answer.arguments[0] as Int / maxBlur.toFloat()
+ `when`(blurUtils.ratioOfBlurRadius(anyFloat())).then { answer ->
+ answer.arguments[0] as Float / maxBlur.toFloat()
}
`when`(blurUtils.supportsBlursOnWindows()).thenReturn(true)
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
@@ -102,7 +103,6 @@
statusBarStateController, blurUtils, biometricUnlockController,
keyguardStateController, choreographer, wallpaperManager,
notificationShadeWindowController, dozeParameters, dumpManager)
- notificationShadeDepthController.shadeSpring = shadeSpring
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
notificationShadeDepthController.root = root
@@ -123,7 +123,6 @@
fun onPanelExpansionChanged_apliesBlur_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
false /* tracking */)
- verify(shadeSpring).animateTo(eq(maxBlur), any())
verify(shadeAnimation).animateTo(eq(maxBlur), any())
}
@@ -170,23 +169,48 @@
}
@Test
+ fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
+ notificationShadeDepthController.panelPullDownMinFraction = 0.5f
+ notificationShadeDepthController.onPanelExpansionChanged(0.5f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
+
+ notificationShadeDepthController.onPanelExpansionChanged(0.75f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0.5f)
+
+ notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(1f)
+ }
+
+ @Test
fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
onPanelExpansionChanged_apliesBlur_ifShade()
- clearInvocations(shadeSpring)
- clearInvocations(shadeAnimation)
+ clearInvocations(choreographer)
statusBarState = StatusBarState.KEYGUARD
statusBarStateListener.onStateChanged(statusBarState)
- verify(shadeSpring).animateTo(eq(0), any())
verify(shadeAnimation).animateTo(eq(0), any())
}
@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(), eq(maxBlur / 2), 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
@@ -207,10 +231,10 @@
fun setFullShadeTransition_appliesBlur_onlyIfSupported() {
reset(blurUtils)
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
- (answer.arguments[0] as Float * maxBlur).toInt()
+ answer.arguments[0] as Float * maxBlur
}
- `when`(blurUtils.ratioOfBlurRadius(anyInt())).then { answer ->
- answer.arguments[0] as Int / maxBlur.toFloat()
+ `when`(blurUtils.ratioOfBlurRadius(anyFloat())).then { answer ->
+ answer.arguments[0] as Float / maxBlur.toFloat()
}
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
@@ -239,16 +263,16 @@
@Test
fun updateBlurCallback_setsBlur_whenExpanded() {
- `when`(shadeSpring.radius).thenReturn(maxBlur)
- `when`(shadeAnimation.radius).thenReturn(maxBlur)
+ notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
}
@Test
fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
- `when`(shadeSpring.radius).thenReturn(maxBlur)
- `when`(shadeAnimation.radius).thenReturn(maxBlur)
+ notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = true
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(0), eq(false))
@@ -293,8 +317,8 @@
// Brightness mirror is fully visible
`when`(brightnessSpring.ratio).thenReturn(1f)
// And shade is blurred
- `when`(shadeSpring.radius).thenReturn(maxBlur)
- `when`(shadeAnimation.radius).thenReturn(maxBlur)
+ notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0))
@@ -304,10 +328,8 @@
@Test
fun ignoreShadeBlurUntilHidden_whennNull_ignoresIfShadeHasNoBlur() {
- `when`(shadeSpring.radius).thenReturn(0)
- `when`(shadeAnimation.radius).thenReturn(0)
+ `when`(shadeAnimation.radius).thenReturn(0f)
notificationShadeDepthController.blursDisabledForAppLaunch = true
- verify(shadeSpring, never()).animateTo(anyInt(), any())
verify(shadeAnimation, never()).animateTo(anyInt(), any())
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 116f807..9e103d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
import com.android.systemui.util.concurrency.FakeExecution
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -90,6 +92,8 @@
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
+ private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock
private lateinit var handler: Handler
@Mock
@@ -107,12 +111,15 @@
private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
@Captor
private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+ @Captor
+ private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener>
private lateinit var sessionListener: OnTargetsAvailableListener
private lateinit var userListener: UserTracker.Callback
private lateinit var settingsObserver: ContentObserver
private lateinit var configChangeListener: ConfigurationListener
private lateinit var statusBarStateListener: StateListener
+ private lateinit var deviceProvisionedListener: DeviceProvisionedListener
private val clock = FakeSystemClock()
private val executor = FakeExecutor(clock)
@@ -144,6 +151,8 @@
`when`(plugin.getView(any())).thenReturn(fakeSmartspaceView)
`when`(userTracker.userProfiles).thenReturn(userList)
`when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
setActiveUser(userHandlePrimary)
setAllowPrivateNotifications(userHandlePrimary, true)
@@ -161,11 +170,15 @@
contentResolver,
configurationController,
statusBarStateController,
+ deviceProvisionedController,
execution,
executor,
handler,
Optional.of(plugin)
)
+
+ verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
+ deviceProvisionedListener = deviceProvisionedCaptor.value
}
@Test(expected = RuntimeException::class)
@@ -180,6 +193,27 @@
}
@Test
+ fun connectOnlyAfterDeviceIsProvisioned() {
+ // GIVEN an unprovisioned device and an attempt to connect
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
+
+ // WHEN a connection attempt is made
+ controller.buildAndConnectView(fakeParent)
+
+ // THEN no session is created
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+ // WHEN it does become provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onUserSetupChanged()
+
+ // THEN the session is created
+ verify(smartspaceManager).createSmartspaceSession(any())
+ }
+
+ @Test
fun testListenersAreRegistered() {
// GIVEN a listener is added after a session is created
connectSession()
@@ -424,6 +458,20 @@
assertEquals(fakeSmartspaceView, controller.view)
}
+ @Test
+ fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
+ // GIVEN an uninitalized smartspaceView
+ // WHEN the device is provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onDeviceProvisionedChanged()
+
+ // THEN no calls to createSmartspaceSession should occur
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+ // THEN no listeners should be registered
+ verify(configurationController, never()).addCallback(any())
+ }
+
private fun connectSession() {
controller.buildAndConnectView(fakeParent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b03df880..4151ab2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -290,6 +290,8 @@
@Test
public void testUpdateFooter_noNotifications() {
setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
+
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
@@ -299,6 +301,7 @@
@Test
public void testUpdateFooter_remoteInput() {
setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
when(row.canViewBeDismissed()).thenReturn(true);
@@ -318,6 +321,7 @@
@Test
public void testUpdateFooter_oneClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
@@ -331,8 +335,25 @@
}
@Test
+ public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
+ setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(false);
+
+ when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
+ when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
+ .thenReturn(true);
+ when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
+
+ FooterView view = mock(FooterView.class);
+ mStackScroller.setFooterView(view);
+ mStackScroller.updateFooter();
+ verify(mStackScroller).updateFooterView(false, true, true);
+ }
+
+ @Test
public void testUpdateFooter_oneNonClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
when(row.canViewBeDismissed()).thenReturn(false);
@@ -351,6 +372,8 @@
@Test
public void testUpdateFooter_atEnd() {
+ mStackScroller.setCurrentUserSetup(true);
+
// add footer
mStackScroller.inflateFooterView();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
index f376e88..42f3889 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -74,6 +74,7 @@
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
@@ -98,6 +99,7 @@
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@Mock private TunerService mTunerService;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private DynamicPrivacyController mDynamicPrivacyController;
@Mock private ConfigurationController mConfigurationController;
@Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@@ -153,6 +155,7 @@
mHeadsUpManager,
mNotificationRoundnessManager,
mTunerService,
+ mDeviceProvisionedController,
mDynamicPrivacyController,
mConfigurationController,
mSysuiStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 60f0b68..4276f7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -274,6 +274,26 @@
}
@Test
+ public void onBiometricAuthenticated_onLockScreen() {
+ // GIVEN not dozing
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+
+ // WHEN we want to unlock collapse
+ mBiometricUnlockController.startWakeAndUnlock(
+ BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
+
+ // THEN we collpase the panels and notify authenticated
+ verify(mShadeController).animateCollapsePanels(
+ /* flags */ anyInt(),
+ /* force */ eq(true),
+ /* delayed */ eq(false),
+ /* speedUpFactor */ anyFloat()
+ );
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(
+ /* strongAuth */ eq(false));
+ }
+
+ @Test
public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() {
reset(mUpdateMonitor);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 690b841..1043faa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -16,24 +16,37 @@
package com.android.systemui.statusbar.phone;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.AdditionalAnswers.returnsFirstArg;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.util.BurnInHelperKt;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private static final int SCREEN_HEIGHT = 2000;
- private static final int EMPTY_MARGIN = 0;
private static final int EMPTY_HEIGHT = 0;
private static final float ZERO_DRAG = 0.f;
private static final float OPAQUE = 1.f;
@@ -41,10 +54,15 @@
private static final boolean HAS_CUSTOM_CLOCK = false;
private static final boolean HAS_VISIBLE_NOTIFS = false;
+ @Mock
+ private Resources mResources;
+
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
+ private MockitoSession mStaticMockSession;
private int mNotificationStackHeight;
private float mPanelExpansion;
+ private int mKeyguardStatusBarHeaderHeight;
private int mKeyguardStatusHeight;
private float mDark;
private boolean mHasCustomClock;
@@ -52,16 +70,32 @@
private float mQsExpansion;
private int mCutoutTopInset = 0; // in pixels
private boolean mIsSplitShade = false;
+ private float mUdfpsTop = -1;
+ private float mClockBottom = SCREEN_HEIGHT / 2;
+ private boolean mClockTopAligned;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession = mockitoSession()
+ .mockStatic(BurnInHelperKt.class)
+ .startMocking();
+
mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+ when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
+ mClockPositionAlgorithm.loadDimens(mResources);
+
mClockPosition = new KeyguardClockPositionAlgorithm.Result();
mHasCustomClock = HAS_CUSTOM_CLOCK;
mHasVisibleNotifs = HAS_VISIBLE_NOTIFS;
}
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
@Test
public void clockPositionTopOfScreenOnAOD() {
// GIVEN on AOD and both stack scroll and clock have 0 height
@@ -338,6 +372,155 @@
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
}
+ @Test
+ public void clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN the clock + udfps are 100px apart
+ mClockBottom = SCREEN_HEIGHT - 500;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the worst-case clock Y position is shifted only by 100 (not the full 200),
+ // so that it's at the same location as mUdfpsTop
+ assertThat(mClockPosition.clockY).isEqualTo(100);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ @Test
+ public void clockPositionShiftsToAvoidUdfpsOnAOD_usesSpaceAboveClock() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD)
+ mKeyguardStatusBarHeaderHeight = 150;
+
+ // GIVEN the bottom of the clock is beyond the top of UDFPS
+ mClockBottom = SCREEN_HEIGHT - 300;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use the area above the clock for
+ // burn-in since the burn in offset > space above clock
+ assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset (0 in this case)
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ @Test
+ public void clockPositionShiftsToAvoidUdfpsOnAOD_usesMaxBurnInOffset() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD) but 50px are taken up by the cutout
+ mKeyguardStatusBarHeaderHeight = 200;
+ mCutoutTopInset = 50;
+
+ // GIVEN the bottom of the clock is beyond the top of UDFPS
+ mClockBottom = SCREEN_HEIGHT - 300;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is only 25px (less than space above clock)
+ givenAOD();
+ int maxYBurnInOffset = 25;
+ givenMaxBurnInOffset(maxYBurnInOffset);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use the area above the clock for
+ // burn-in
+ assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts above mKeyguardStatusBarHeaderHeight
+ assertThat(mClockPosition.clockY).isEqualTo(
+ mKeyguardStatusBarHeaderHeight - 2 * maxYBurnInOffset);
+ }
+
+ @Test
+ public void clockPositionShiftsToMaximizeUdfpsBurnInMovement() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD) but 50px are taken up by the cutout
+ mKeyguardStatusBarHeaderHeight = 200;
+ mCutoutTopInset = 50;
+ int upperSpaceAvailable = mKeyguardStatusBarHeaderHeight - mCutoutTopInset;
+
+ // GIVEN the bottom of the clock and the top of UDFPS are 100px apart
+ mClockBottom = SCREEN_HEIGHT - 500;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+ float lowerSpaceAvailable = mUdfpsTop - mClockBottom;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use both the area above
+ // the clock and below the clock (vertically centered in its allowed area)
+ assertThat(mClockPosition.clockY).isEqualTo(
+ (int) (mCutoutTopInset + upperSpaceAvailable + lowerSpaceAvailable));
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ private void givenHighestBurnInOffset() {
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg());
+ }
+
+ private void givenLowestBurnInOffset() {
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(0);
+ }
+
+ private void givenMaxBurnInOffset(int offset) {
+ when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_large_clock))
+ .thenReturn(offset);
+ mClockPositionAlgorithm.loadDimens(mResources);
+ }
+
private void givenAOD() {
mPanelExpansion = 1.f;
mDark = 1.f;
@@ -348,13 +531,33 @@
mDark = 0.f;
}
+ /**
+ * Setup and run the clock position algorithm.
+ *
+ * mClockPosition.clockY will contain the top y-coordinate for the clock position
+ */
private void positionClock() {
- mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
- mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
- 0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */,
- mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
- 0 /* unlockedStackScrollerPadding */, mQsExpansion,
- mCutoutTopInset, mIsSplitShade);
+ mClockPositionAlgorithm.setup(
+ mKeyguardStatusBarHeaderHeight,
+ SCREEN_HEIGHT,
+ mNotificationStackHeight,
+ mPanelExpansion,
+ SCREEN_HEIGHT,
+ mKeyguardStatusHeight,
+ 0 /* userSwitchHeight */,
+ 0 /* userSwitchPreferredY */,
+ mHasCustomClock,
+ mHasVisibleNotifs,
+ mDark,
+ ZERO_DRAG,
+ false /* bypassEnabled */,
+ 0 /* unlockedStackScrollerPadding */,
+ mQsExpansion,
+ mCutoutTopInset,
+ mIsSplitShade,
+ mUdfpsTop,
+ mClockBottom,
+ mClockTopAligned);
mClockPositionAlgorithm.run(mClockPosition);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index d7661be..f247788 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -141,7 +141,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class NotificationPanelViewTest extends SysuiTestCase {
+public class NotificationPanelViewControllerTest extends SysuiTestCase {
private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50;
@@ -470,6 +470,12 @@
}
@Test
+ public void testSetMinFraction() {
+ mNotificationPanelViewController.setMinFraction(0.5f);
+ verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
+ }
+
+ @Test
public void testSetDozing_notifiesNsslAndStateController() {
mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */,
null /* touch */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index ddd7854..90b8a74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -146,7 +146,7 @@
mNotificationShadeWindowController.attach();
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.setLightRevealScrimAmount(0f);
+ mNotificationShadeWindowController.setLightRevealScrimOpaque(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) == 0).isTrue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index 6c1a3c9..aafaebd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -16,6 +16,11 @@
package com.android.systemui.statusbar.phone;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,6 +32,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.LockIconViewController;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
@@ -55,6 +61,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -90,6 +98,11 @@
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ @Mock private LockIconViewController mLockIconViewController;
+
+ @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
+ mInteractionEventHandlerCaptor;
+ private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler;
@Before
public void setUp() {
@@ -131,7 +144,8 @@
mNotificationPanelViewController,
mStatusBarViewFactory,
mNotificationStackScrollLayoutController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mLockIconViewController);
mController.setupExpandedStatusBar();
mController.setService(mStatusBar, mNotificationShadeWindowController);
mController.setDragDownHelper(mDragDownHelper);
@@ -147,4 +161,49 @@
verify(mDragDownHelper).onTouchEvent(ev);
ev.recycle();
}
+
+ @Test
+ public void testInterceptTouchWhenShowingAltAuth() {
+ captureInteractionEventHandler();
+
+ // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we should intercept touch
+ assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
+ }
+
+ @Test
+ public void testNoInterceptTouch() {
+ captureInteractionEventHandler();
+
+ // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we shouldn't intercept touch
+ assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
+ }
+
+ @Test
+ public void testHandleTouchEventWhenShowingAltAuth() {
+ captureInteractionEventHandler();
+
+ // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we should handle the touch
+ assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class)));
+ }
+
+ private void captureInteractionEventHandler() {
+ verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture());
+ mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue();
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 678b193..47c8806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -621,7 +624,7 @@
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, false,
+ mScrimBehind, true,
mScrimForBubble, true
));
@@ -702,7 +705,7 @@
public void qsExpansion_half_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
- mScrimController.setQsPosition(0.5f, 999 /* value doesn't matter */);
+ mScrimController.setQsPosition(0.25f, 999 /* value doesn't matter */);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -734,20 +737,13 @@
}
@Test
- public void transitionToUnlockedFromAod() {
- // Simulate unlock with fingerprint
- mScrimController.transitionTo(ScrimState.AOD);
+ public void transitionToUnlockedFromOff() {
+ // Simulate unlock with fingerprint without AOD
+ mScrimController.transitionTo(ScrimState.OFF);
mScrimController.setPanelExpansion(0f);
finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
- // Immediately tinted black after the transition starts
- assertScrimTinted(Map.of(
- mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, true
- ));
-
finishAnimationsImmediately();
// All scrims should be transparent at the end of fade transition.
@@ -765,6 +761,28 @@
}
@Test
+ public void transitionToUnlockedFromAod() {
+ // Simulate unlock with fingerprint
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.setPanelExpansion(0f);
+ finishAnimationsImmediately();
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+
+ finishAnimationsImmediately();
+
+ // All scrims should be transparent at the end of fade transition.
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mScrimBehind, TRANSPARENT));
+
+ // Make sure at the very end of the animation, we're reset to transparent
+ assertScrimTinted(Map.of(
+ mScrimInFront, false,
+ mScrimBehind, true
+ ));
+ }
+
+ @Test
public void scrimBlanksBeforeLeavingAod() {
// Simulate unlock with fingerprint
mScrimController.transitionTo(ScrimState.AOD);
@@ -1081,6 +1099,26 @@
}
@Test
+ public void testDoesntAnimate_whenUnlocking() {
+ // LightRevealScrim will animate the transition, we should only hide the keyguard scrims.
+ ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+ ScrimState.UNLOCKED.prepare(ScrimState.PULSING);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse();
+
+ ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+ ScrimState.UNLOCKED.prepare(ScrimState.AOD);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse();
+
+ // LightRevealScrim doesn't animate when AOD is disabled. We need to use the legacy anim.
+ ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+ ScrimState.UNLOCKED.prepare(ScrimState.OFF);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+ }
+
+ @Test
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -1098,7 +1136,7 @@
@Test
public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
- mScrimController.setQsPosition(0.5f, 300);
+ mScrimController.setQsPosition(0.25f, 300);
assertScrimAlpha(Map.of(
mScrimBehind, SEMI_TRANSPARENT,
@@ -1138,6 +1176,21 @@
}
@Test
+ public void testNotificationTransparency_unnocclusion() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.setUnocclusionAnimationRunning(true);
+
+ assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA,
+ /* expansion */ 0.0f);
+ assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA,
+ /* expansion */ 1.0f);
+
+ // Verify normal behavior after
+ mScrimController.setUnocclusionAnimationRunning(false);
+ assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.2f, /* expansion */ 0.4f);
+ }
+
+ @Test
public void testNotificationTransparency_inKeyguardState() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index c39a906..2b569f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -299,18 +299,4 @@
verify(mBouncer).updateKeyguardPosition(1.0f);
}
-
- @Test
- public void testNavBarHiddenWhenSleepAnimationStarts() {
- mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- assertTrue(mStatusBarKeyguardViewManager.isNavBarVisible());
-
- // Verify that the nav bar is hidden when the screen off animation starts
- doReturn(true).when(mUnlockedScreenOffAnimationController).isScreenOffAnimationPlaying();
- mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
- assertFalse(mStatusBarKeyguardViewManager.isNavBarVisible());
-
- mWakefulnessLifecycle.dispatchFinishedWakingUp();
- assertTrue(mStatusBarKeyguardViewManager.isNavBarVisible());
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 2c2833a..6051c56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -812,6 +812,58 @@
}
@Test
+ public void testTransitionLaunch_goesToUnlocked() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ mStatusBar.showKeyguardImpl();
+
+ // Starting a pulse should change the scrim controller to the pulsing state
+ when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
+ when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
+ }
+
+ @Test
+ public void testSetExpansionAffectsAlpha_onlyWhenHidingKeyguard() {
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(true));
+
+ clearInvocations(mScrimController);
+ when(mBiometricUnlockController.isBiometricUnlock()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(true));
+
+ clearInvocations(mScrimController);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+
+ clearInvocations(mScrimController);
+ reset(mKeyguardStateController);
+ when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+
+ clearInvocations(mScrimController);
+ reset(mKeyguardStateController);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+ }
+
+ @Test
+ public void testTransitionLaunch_noPreview_doesntGoUnlocked() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ mStatusBar.showKeyguardImpl();
+
+ // Starting a pulse should change the scrim controller to the pulsing state
+ when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
+ when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
+ }
+
+ @Test
public void testSetOccluded_propagatesToScrimController() {
mStatusBar.setOccluded(true);
verify(mScrimController).setKeyguardOccluded(eq(true));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index d26db4c..b7c4d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -221,6 +221,18 @@
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
+ /** Regression test for b/201097913. */
+ @Test
+ fun onEntryCleanUp_callNotifAddedThenRemoved_listenerNotified() {
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockOngoingCallListener)
+
+ notifCollectionListener.onEntryCleanUp(ongoingCallNotifEntry)
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
+ }
+
/** Regression test for b/188491504. */
@Test
fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 57198db..4a5770d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -238,7 +238,7 @@
mNetworkController.setNoNetworksAvailable(false);
setWifiStateForVcn(true, testSsid);
setWifiLevelForVcn(0);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]);
+ verifyLastMobileDataIndicatorsForVcn(true, 0, TelephonyIcons.ICON_CWF, false);
mNetworkController.setNoNetworksAvailable(true);
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
@@ -246,11 +246,11 @@
setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
+ verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
+ verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
new file mode 100644
index 0000000..871a48c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Date
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class VariableDateViewControllerTest : SysuiTestCase() {
+
+ companion object {
+ private const val TIME_STAMP = 1_500_000_000_000
+ private const val LONG_PATTERN = "EEEMMMd"
+ private const val SHORT_PATTERN = "MMMd"
+ private const val CHAR_WIDTH = 10f
+ }
+
+ @Mock
+ private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock
+ private lateinit var view: VariableDateView
+ @Captor
+ private lateinit var onMeasureListenerCaptor: ArgumentCaptor<VariableDateView.OnMeasureListener>
+
+ private var lastText: String? = null
+
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var testableHandler: Handler
+ private lateinit var controller: VariableDateViewController
+
+ private lateinit var longText: String
+ private lateinit var shortText: String
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ testableHandler = Handler(testableLooper.looper)
+
+ systemClock = FakeSystemClock()
+ systemClock.setCurrentTimeMillis(TIME_STAMP)
+
+ `when`(view.longerPattern).thenReturn(LONG_PATTERN)
+ `when`(view.shorterPattern).thenReturn(SHORT_PATTERN)
+ `when`(view.handler).thenReturn(testableHandler)
+
+ `when`(view.setText(anyString())).thenAnswer {
+ lastText = it.arguments[0] as? String
+ Unit
+ }
+ `when`(view.isAttachedToWindow).thenReturn(true)
+
+ val date = Date(TIME_STAMP)
+ longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN))
+ shortText = getTextForFormat(date, getFormatFromPattern(SHORT_PATTERN))
+
+ // Assume some sizes for the text, the controller doesn't need to know if these sizes are
+ // the true ones
+ `when`(view.getDesiredWidthForText(any())).thenAnswer {
+ getTextLength(it.arguments[0] as CharSequence)
+ }
+
+ controller = VariableDateViewController(
+ systemClock,
+ broadcastDispatcher,
+ testableHandler,
+ view
+ )
+
+ controller.init()
+ testableLooper.processAllMessages()
+
+ verify(view).onAttach(capture(onMeasureListenerCaptor))
+ }
+
+ @Test
+ fun testViewStartsWithLongText() {
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testListenerNotNull() {
+ assertThat(onMeasureListenerCaptor.value).isNotNull()
+ }
+
+ @Test
+ fun testLotsOfSpaceUseLongText() {
+ onMeasureListenerCaptor.value.onMeasureAction(10000)
+
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testSmallSpaceUseEmpty() {
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEmpty()
+ }
+
+ @Test
+ fun testSpaceInBetweenUseShortText() {
+ val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
+
+ onMeasureListenerCaptor.value.onMeasureAction(average)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEqualTo(shortText)
+ }
+
+ @Test
+ fun testSwitchBackToLonger() {
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+
+ onMeasureListenerCaptor.value.onMeasureAction(10000)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testNoSwitchingWhenFrozen() {
+ `when`(view.freezeSwitching).thenReturn(true)
+
+ val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
+ onMeasureListenerCaptor.value.onMeasureAction(average)
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ private fun getTextLength(text: CharSequence): Float {
+ return text.length * CHAR_WIDTH
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 9c47f19..2c461ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -103,7 +103,8 @@
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
+ mManager = new ThemeOverlayApplier(mOverlayManager,
+ MoreExecutors.directExecutor(), MoreExecutors.directExecutor(),
LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
@Override
protected OverlayManagerTransaction.Builder getTransactionBuilder() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
new file mode 100644
index 0000000..eebcbe6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.usb
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.hardware.usb.IUsbSerialReader
+import android.hardware.usb.UsbAccessory
+import android.hardware.usb.UsbManager
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.Exception
+
+/**
+ * UsbPermissionActivityTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UsbPermissionActivityTest : SysuiTestCase() {
+
+ class UsbPermissionActivityTestable : UsbPermissionActivity()
+
+ @Rule
+ @JvmField
+ var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
+ UsbPermissionActivityTestable::class.java, false, false)
+
+ private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
+ .apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra(UsbManager.EXTRA_PACKAGE, "com.android.systemui")
+ putExtra(Intent.EXTRA_INTENT, PendingIntent.getBroadcast(
+ mContext,
+ 334,
+ Intent("NO_ACTION"),
+ PendingIntent.FLAG_MUTABLE))
+ putExtra(UsbManager.EXTRA_ACCESSORY, UsbAccessory(
+ "manufacturer",
+ "model",
+ "description",
+ "version",
+ "uri",
+ object : IUsbSerialReader.Stub() {
+ override fun getSerial(packageName: String): String {
+ return "serial"
+ }
+ }))
+ }
+
+ @Before
+ fun setUp() {
+ activityRule.launchActivity(activityIntent)
+ }
+
+ @After
+ fun tearDown() {
+ activityRule.finishActivity()
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testHideNonSystemOverlay() {
+ assertThat(activityRule.activity.window.attributes.privateFlags and
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ .isEqualTo(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
index a34c598..0e9d96c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.util.sensors;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -338,30 +340,25 @@
@Test
public void testSecondaryCancelsSecondary() {
TestableListener listener = new TestableListener();
- ThresholdSensor.Listener cancelingListener = new ThresholdSensor.Listener() {
- @Override
- public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) {
- mProximitySensor.pause();
- }
- };
+ ThresholdSensor.Listener cancelingListener = event -> mProximitySensor.pause();
mProximitySensor.register(listener);
mProximitySensor.register(cancelingListener);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorPrimary.triggerEvent(true, 0);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(true, 0);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
// The proximity sensor should now be canceled. Advancing the clock should do nothing.
- assertEquals(0, mFakeExecutor.numPending());
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(false, 1);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
mProximitySensor.unregister(listener);
}
@@ -372,33 +369,66 @@
TestableListener listener = new TestableListener();
- // WE immediately register the secondary sensor.
+ // We immediately register the secondary sensor.
mProximitySensor.register(listener);
- assertFalse(mThresholdSensorPrimary.isPaused());
- assertFalse(mThresholdSensorSecondary.isPaused());
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorPrimary.triggerEvent(true, 0);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(true, 0);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
// The secondary sensor should now remain resumed indefinitely.
- assertFalse(mThresholdSensorSecondary.isPaused());
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
mThresholdSensorSecondary.triggerEvent(false, 1);
- assertFalse(listener.mLastEvent.getBelow());
- assertEquals(2, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isFalse();
+ assertThat(listener.mCallCount).isEqualTo(2);
// The secondary is still running, and not polling with the executor.
- assertFalse(mThresholdSensorSecondary.isPaused());
- assertEquals(0, mFakeExecutor.numPending());
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
mProximitySensor.unregister(listener);
}
+ @Test
+ public void testSecondaryPausesPrimary() {
+ TestableListener listener = new TestableListener();
+
+ mProximitySensor.register(listener);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isFalse();
+ assertThat(mThresholdSensorSecondary.isPaused()).isTrue();
+
+ mProximitySensor.setSecondarySafe(true);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ }
+
+ @Test
+ public void testSecondaryResumesPrimary() {
+ mProximitySensor.setSecondarySafe(true);
+
+ TestableListener listener = new TestableListener();
+ mProximitySensor.register(listener);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+
+ mProximitySensor.setSecondarySafe(false);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isFalse();
+ assertThat(mThresholdSensorSecondary.isPaused()).isTrue();
+
+
+ }
+
private static class TestableListener implements ThresholdSensor.Listener {
ThresholdSensor.ThresholdSensorEvent mLastEvent;
int mCallCount = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
index 6976422..7bb2674 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
@@ -31,6 +31,7 @@
private final Map<SettingsKey, String> mValues = new HashMap<>();
private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
new HashMap<>();
+ private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
@@ -55,9 +56,15 @@
@Override
public void registerContentObserverForUser(Uri uri, boolean notifyDescendents,
ContentObserver settingsObserver, int userHandle) {
- SettingsKey key = new SettingsKey(userHandle, uri.toString());
- mContentObservers.putIfAbsent(key, new ArrayList<>());
- List<ContentObserver> observers = mContentObservers.get(key);
+ List<ContentObserver> observers;
+ if (userHandle == UserHandle.USER_ALL) {
+ mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
+ observers = mContentObserversAllUsers.get(uri.toString());
+ } else {
+ SettingsKey key = new SettingsKey(userHandle, uri.toString());
+ mContentObservers.putIfAbsent(key, new ArrayList<>());
+ observers = mContentObservers.get(key);
+ }
observers.add(settingsObserver);
}
@@ -67,6 +74,10 @@
List<ContentObserver> observers = mContentObservers.get(key);
observers.remove(settingsObserver);
}
+ for (String key : mContentObserversAllUsers.keySet()) {
+ List<ContentObserver> observers = mContentObserversAllUsers.get(key);
+ observers.remove(settingsObserver);
+ }
}
@Override
@@ -114,6 +125,10 @@
for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
observer.dispatchChange(false, List.of(uri), userHandle);
}
+ for (ContentObserver observer :
+ mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
+ observer.dispatchChange(false, List.of(uri), userHandle);
+ }
return true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 0d560f2..34cae58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.verify;
import android.database.ContentObserver;
+import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
@@ -89,6 +90,16 @@
}
@Test
+ public void testRegisterContentObserverAllUsers() {
+ mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
+
+ mFakeSettings.putString("cat", "hat");
+
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+ }
+
+ @Test
public void testUnregisterContentObserver() {
mFakeSettings.registerContentObserver("cat", mContentObserver);
mFakeSettings.unregisterContentObserver(mContentObserver);
@@ -98,4 +109,16 @@
verify(mContentObserver, never()).dispatchChange(
anyBoolean(), any(Collection.class), anyInt());
}
+
+ @Test
+ public void testUnregisterContentObserverAllUsers() {
+ mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
+ mFakeSettings.unregisterContentObserver(mContentObserver);
+
+ mFakeSettings.putString("cat", "hat");
+
+ verify(mContentObserver, never()).dispatchChange(
+ anyBoolean(), any(Collection.class), anyInt());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index be11024..4f9cb35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -51,6 +51,11 @@
}
@Override
+ public boolean isCameraRotationEnabled() {
+ return false;
+ }
+
+ @Override
public void setRotationLockedAtAngle(boolean locked, int rotation) {
}
diff --git a/packages/services/CameraExtensionsProxy/AndroidManifest.xml b/packages/services/CameraExtensionsProxy/AndroidManifest.xml
index d356894..ef1d581 100644
--- a/packages/services/CameraExtensionsProxy/AndroidManifest.xml
+++ b/packages/services/CameraExtensionsProxy/AndroidManifest.xml
@@ -8,6 +8,7 @@
android:directBootAware="true">
<service android:name=".CameraExtensionsProxyService"
+ android:visibleToInstantApps="true"
android:exported="true">
</service>
<uses-library android:name="androidx.camera.extensions.impl" android:required="false" />
diff --git a/proto/src/metrics_constants/OWNERS b/proto/src/metrics_constants/OWNERS
index 7009282..274d0d6 100644
--- a/proto/src/metrics_constants/OWNERS
+++ b/proto/src/metrics_constants/OWNERS
@@ -1,4 +1,3 @@
cwren@android.com
yanglu@google.com
yaochen@google.com
-yro@google.com
diff --git a/services/Android.bp b/services/Android.bp
index e11b044..841edc7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -221,19 +221,13 @@
},
dists: [
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/system-server/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: [
- "sdk",
- "win_sdk",
- ],
+ targets: ["sdk"],
dir: "apistubs/android/system-server/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
diff --git a/services/OWNERS b/services/OWNERS
index a083319..9b5f0e7 100644
--- a/services/OWNERS
+++ b/services/OWNERS
@@ -3,5 +3,7 @@
# art-team@ manages the system server profile
per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com
-per-file java/com/android/server/* = toddke@google.com,patb@google.com
+per-file java/com/android/server/* = patb@google.com
per-file tests/servicestests/src/com/android/server/systemconfig/* = patb@google.com
+
+per-file proguard.flags = jdduke@google.com
diff --git a/services/art-profile b/services/art-profile
index af58bca..2d9e95e 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -1464,7 +1464,7 @@
HPLcom/android/server/DeviceIdleController;->resetIdleManagementLocked()V+]Lcom/android/server/AnyMotionDetector;Lcom/android/server/AnyMotionDetector;]Lcom/android/server/DeviceIdleController;Lcom/android/server/DeviceIdleController;
HPLcom/android/server/DeviceIdleController;->resetLightIdleManagementLocked()V+]Lcom/android/server/DeviceIdleController;Lcom/android/server/DeviceIdleController;
HPLcom/android/server/DeviceIdleController;->scheduleAlarmLocked(JZ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;
-HPLcom/android/server/DeviceIdleController;->scheduleLightAlarmLocked(JJ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;
+HPLcom/android/server/DeviceIdleController;->scheduleLightAlarmLocked(JJZ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;
HPLcom/android/server/DeviceIdleController;->scheduleMotionRegistrationAlarmLocked()V+]Lcom/android/server/DeviceIdleController$Injector;Lcom/android/server/DeviceIdleController$Injector;]Landroid/app/AlarmManager;Landroid/app/AlarmManager;
HSPLcom/android/server/DeviceIdleController;->scheduleMotionTimeoutAlarmLocked()V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;]Lcom/android/server/DeviceIdleController$Injector;Lcom/android/server/DeviceIdleController$Injector;
HPLcom/android/server/DeviceIdleController;->scheduleReportActiveLocked(Ljava/lang/String;I)V+]Lcom/android/server/DeviceIdleController$MyHandler;Lcom/android/server/DeviceIdleController$MyHandler;
diff --git a/services/cloudsearch/OWNERS b/services/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/services/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1dda14c..7a4f1de 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -154,6 +154,7 @@
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.1-java",
"android.hardware.contexthub-V1.0-java",
+ "android.hardware.ir-V1-java",
"android.hardware.rebootescrow-V1-java",
"android.hardware.soundtrigger-V2.3-java",
"android.hardware.power.stats-V1-java",
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 8860a81..c8b4f11 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -46,6 +46,7 @@
import android.bluetooth.IBluetoothManagerCallback;
import android.bluetooth.IBluetoothProfileServiceConnection;
import android.bluetooth.IBluetoothStateChangeCallback;
+import android.bluetooth.IBluetoothLeCallControl;
import android.content.ActivityNotFoundException;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
@@ -1328,11 +1329,15 @@
+ bluetoothProfile);
}
- if (bluetoothProfile != BluetoothProfile.HEADSET) {
+ Intent intent;
+ if (bluetoothProfile == BluetoothProfile.HEADSET) {
+ intent = new Intent(IBluetoothHeadset.class.getName());
+ } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) {
+ intent = new Intent(IBluetoothLeCallControl.class.getName());
+ } else {
return false;
}
- Intent intent = new Intent(IBluetoothHeadset.class.getName());
psc = new ProfileServiceConnections(intent);
if (!psc.bindService()) {
return false;
@@ -1850,6 +1855,10 @@
mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
mEnable = true;
+ if (isBle == 0) {
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ }
+
// Use service interface to get the exact state
try {
mBluetoothLock.readLock().lock();
@@ -1863,7 +1872,6 @@
} else {
Slog.w(TAG, "BT Enable in BLE_ON State, going to ON");
mBluetooth.onLeServiceUp(mContext.getAttributionSource());
- persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
}
break;
case BluetoothAdapter.STATE_BLE_TURNING_ON:
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 469f465..48f5b51 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -488,7 +488,7 @@
} else {
final String headers = getBootHeadersToLogAndUpdate();
addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
- TAG_TOMBSTONE);
+ TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java
index 2ed6c77..c4e84a4 100644
--- a/services/core/java/com/android/server/ConsumerIrService.java
+++ b/services/core/java/com/android/server/ConsumerIrService.java
@@ -19,17 +19,19 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.IConsumerIrService;
+import android.hardware.ir.ConsumerIrFreqRange;
+import android.hardware.ir.IConsumerIr;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Slog;
-import java.lang.RuntimeException;
-
public class ConsumerIrService extends IConsumerIrService.Stub {
private static final String TAG = "ConsumerIrService";
private static final int MAX_XMIT_TIME = 2000000; /* in microseconds */
- private static native boolean halOpen();
+ private static native boolean getHidlHalService();
private static native int halTransmit(int carrierFrequency, int[] pattern);
private static native int[] halGetCarrierFrequencies();
@@ -37,6 +39,7 @@
private final PowerManager.WakeLock mWakeLock;
private final boolean mHasNativeHal;
private final Object mHalLock = new Object();
+ private IConsumerIr mAidlService = null;
ConsumerIrService(Context context) {
mContext = context;
@@ -45,7 +48,8 @@
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mWakeLock.setReferenceCounted(true);
- mHasNativeHal = halOpen();
+ mHasNativeHal = getHalService();
+
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONSUMER_IR)) {
if (!mHasNativeHal) {
throw new RuntimeException("FEATURE_CONSUMER_IR present, but no IR HAL loaded!");
@@ -60,6 +64,19 @@
return mHasNativeHal;
}
+ private boolean getHalService() {
+ // Attempt to get the AIDL HAL service first
+ final String fqName = IConsumerIr.DESCRIPTOR + "/default";
+ mAidlService = IConsumerIr.Stub.asInterface(
+ ServiceManager.waitForDeclaredService(fqName));
+ if (mAidlService != null) {
+ return true;
+ }
+
+ // Fall back to the HIDL HAL service
+ return getHidlHalService();
+ }
+
private void throwIfNoIrEmitter() {
if (!mHasNativeHal) {
throw new UnsupportedOperationException("IR emitter not available");
@@ -91,10 +108,18 @@
// Right now there is no mechanism to ensure fair queing of IR requests
synchronized (mHalLock) {
- int err = halTransmit(carrierFrequency, pattern);
+ if (mAidlService != null) {
+ try {
+ mAidlService.transmit(carrierFrequency, pattern);
+ } catch (RemoteException ignore) {
+ Slog.e(TAG, "Error transmitting frequency: " + carrierFrequency);
+ }
+ } else {
+ int err = halTransmit(carrierFrequency, pattern);
- if (err < 0) {
- Slog.e(TAG, "Error transmitting: " + err);
+ if (err < 0) {
+ Slog.e(TAG, "Error transmitting: " + err);
+ }
}
}
}
@@ -109,7 +134,24 @@
throwIfNoIrEmitter();
synchronized(mHalLock) {
- return halGetCarrierFrequencies();
+ if (mAidlService != null) {
+ try {
+ ConsumerIrFreqRange[] output = mAidlService.getCarrierFreqs();
+ if (output.length <= 0) {
+ Slog.e(TAG, "Error getting carrier frequencies.");
+ }
+ int[] result = new int[output.length * 2];
+ for (int i = 0; i < output.length; i++) {
+ result[i * 2] = output[i].minHz;
+ result[i * 2 + 1] = output[i].maxHz;
+ }
+ return result;
+ } catch (RemoteException ignore) {
+ return null;
+ }
+ } else {
+ return halGetCarrierFrequencies();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 483250a..68fd0c1 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -92,6 +92,8 @@
27533 notification_autogrouped (key|3)
# notification was removed from an autogroup
275534 notification_unautogrouped (key|3)
+# when a notification is adjusted via assistant
+27535 notification_adjusted (key|3),(adjustment_type|3),(new_value|3)
# ---------------------------
# Watchdog.java
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c3543e7..c1c9fbb 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -327,18 +327,23 @@
}
}
- private static int getMaxRescueLevel() {
- return SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)
- ? LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS : LEVEL_FACTORY_RESET;
+ private static int getMaxRescueLevel(boolean mayPerformFactoryReset) {
+ if (!mayPerformFactoryReset
+ || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+ return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
+ }
+ return LEVEL_FACTORY_RESET;
}
/**
* Get the rescue level to perform if this is the n-th attempt at mitigating failure.
*
* @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
+ * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given
+ * failure.
* @return the rescue level for the n-th mitigation attempt.
*/
- private static int getRescueLevel(int mitigationCount) {
+ private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) {
if (mitigationCount == 1) {
return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
} else if (mitigationCount == 2) {
@@ -346,9 +351,9 @@
} else if (mitigationCount == 3) {
return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
} else if (mitigationCount == 4) {
- return Math.min(getMaxRescueLevel(), LEVEL_WARM_REBOOT);
+ return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT);
} else if (mitigationCount >= 5) {
- return Math.min(getMaxRescueLevel(), LEVEL_FACTORY_RESET);
+ return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET);
} else {
Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
return LEVEL_NONE;
@@ -614,7 +619,8 @@
@FailureReasons int failureReason, int mitigationCount) {
if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+ mayPerformFactoryReset(failedPackage)));
} else {
return PackageHealthObserverImpact.USER_IMPACT_NONE;
}
@@ -628,7 +634,8 @@
}
if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
- final int level = getRescueLevel(mitigationCount);
+ final int level = getRescueLevel(mitigationCount,
+ mayPerformFactoryReset(failedPackage));
executeRescueLevel(mContext,
failedPackage == null ? null : failedPackage.getPackageName(), level);
return true;
@@ -653,12 +660,7 @@
} catch (PackageManager.NameNotFoundException ignore) {
}
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
+ return isPersistentSystemApp(packageName);
}
@Override
@@ -666,7 +668,7 @@
if (isDisabled()) {
return PackageHealthObserverImpact.USER_IMPACT_NONE;
}
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
}
@Override
@@ -674,7 +676,8 @@
if (isDisabled()) {
return false;
}
- executeRescueLevel(mContext, /*failedPackage=*/ null, getRescueLevel(mitigationCount));
+ executeRescueLevel(mContext, /*failedPackage=*/ null,
+ getRescueLevel(mitigationCount, true));
return true;
}
@@ -683,6 +686,29 @@
return NAME;
}
+ /**
+ * Returns {@code true} if the failing package is non-null and performing a reboot or
+ * prompting a factory reset is an acceptable mitigation strategy for the package's
+ * failure, {@code false} otherwise.
+ */
+ private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) {
+ if (failingPackage == null) {
+ return false;
+ }
+
+ return isPersistentSystemApp(failingPackage.getPackageName());
+ }
+
+ private boolean isPersistentSystemApp(@NonNull String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
@NonNull String namespace) {
// Record it in calling packages to namespace map
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 91b2440..06a78c8 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -39,6 +39,7 @@
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
+import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
@@ -195,7 +196,7 @@
private EmergencyCallHelper mEmergencyCallHelper;
private KeyguardManager mKeyguardManager;
- private int mCurrentUser = -1;
+ private int mCurrentUser = USER_NULL;
public SensorPrivacyService(Context context) {
super(context);
@@ -228,9 +229,9 @@
@Override
public void onUserStarting(TargetUser user) {
- if (mCurrentUser == -1) {
+ if (mCurrentUser == USER_NULL) {
mCurrentUser = user.getUserIdentifier();
- mSensorPrivacyServiceImpl.userSwitching(-1, user.getUserIdentifier());
+ mSensorPrivacyServiceImpl.userSwitching(USER_NULL, user.getUserIdentifier());
}
}
@@ -420,9 +421,12 @@
}
synchronized (mLock) {
- if (mSuppressReminders.containsKey(new Pair<>(sensor, user))) {
+ UserHandle parentUser = UserHandle.of(mUserManagerInternal
+ .getProfileParentId(user.getIdentifier()));
+ if (mSuppressReminders.containsKey(new Pair<>(sensor, parentUser))) {
Log.d(TAG,
- "Suppressed sensor privacy reminder for " + packageName + "/" + user);
+ "Suppressed sensor privacy reminder for " + packageName + "/"
+ + parentUser);
return;
}
}
@@ -708,6 +712,16 @@
@Override
public void setIndividualSensorPrivacy(@UserIdInt int userId,
@SensorPrivacyManager.Sources.Source int source, int sensor, boolean enable) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " setIndividualSensorPrivacy("
+ + "userId=" + userId
+ + " source=" + source
+ + " sensor=" + sensor
+ + " enable=" + enable
+ + ")");
+ }
enforceManageSensorPrivacyPermission();
if (userId == UserHandle.USER_CURRENT) {
userId = mCurrentUser;
@@ -892,6 +906,14 @@
@Override
public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " isIndividualSensorPrivacyEnabled("
+ + "userId=" + userId
+ + " sensor=" + sensor
+ + ")");
+ }
enforceObserveSensorPrivacyPermission();
if (userId == UserHandle.USER_CURRENT) {
userId = mCurrentUser;
@@ -1273,13 +1295,13 @@
micState = isIndividualSensorPrivacyEnabledLocked(to, MICROPHONE);
camState = isIndividualSensorPrivacyEnabledLocked(to, CAMERA);
}
- if (prevMicState != micState) {
+ if (from == USER_NULL || prevMicState != micState) {
mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState);
setGlobalRestriction(MICROPHONE, micState);
}
- if (prevCamState != camState) {
+ if (from == USER_NULL || prevCamState != camState) {
mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState);
- setGlobalRestriction(CAMERA, micState);
+ setGlobalRestriction(CAMERA, camState);
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 32f6496..2d6170b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -221,6 +221,9 @@
@GuardedBy("mLock")
private final Set<Integer> mFuseMountedUser = new ArraySet<>();
+ @GuardedBy("mLock")
+ private final Set<Integer> mCeStoragePreparedUsers = new ArraySet<>();
+
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
@@ -4866,5 +4869,19 @@
}
return primaryVolumeIds;
}
+
+ @Override
+ public void markCeStoragePrepared(int userId) {
+ synchronized (mLock) {
+ mCeStoragePreparedUsers.add(userId);
+ }
+ }
+
+ @Override
+ public boolean isCeStoragePrepared(int userId) {
+ synchronized (mLock) {
+ return mCeStoragePreparedUsers.contains(userId);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 5ed6c86..811f2f5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -91,6 +91,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.ITelephonyRegistry;
@@ -106,6 +107,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -149,9 +151,12 @@
IPhoneStateListener callback;
IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
+ ICarrierPrivilegesListener carrierPrivilegesListener;
int callerUid;
int callerPid;
+ boolean renounceFineLocationAccess;
+ boolean renounceCoarseLocationAccess;
Set<Integer> eventList;
@@ -171,6 +176,10 @@
return (onOpportunisticSubscriptionsChangedListenerCallback != null);
}
+ boolean matchCarrierPrivilegesListener() {
+ return carrierPrivilegesListener != null;
+ }
+
boolean canReadCallLog() {
try {
return TelephonyPermissions.checkReadCallLog(
@@ -187,8 +196,9 @@
+ " onSubscriptionsChangedListenererCallback="
+ onSubscriptionsChangedListenerCallback
+ " onOpportunisticSubscriptionsChangedListenererCallback="
- + onOpportunisticSubscriptionsChangedListenerCallback + " subId=" + subId
- + " phoneId=" + phoneId + " events=" + eventList + "}";
+ + onOpportunisticSubscriptionsChangedListenerCallback
+ + " carrierPrivilegesListener=" + carrierPrivilegesListener
+ + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
}
}
@@ -364,7 +374,7 @@
private List<BarringInfo> mBarringInfo = null;
- private boolean mCarrierNetworkChangeState = false;
+ private boolean[] mCarrierNetworkChangeState = null;
private PhoneCapability mPhoneCapability = null;
@@ -400,6 +410,10 @@
*/
private List<Map<Pair<Integer, ApnSetting>, PreciseDataConnectionState>>
mPreciseDataConnectionStates;
+
+ /** Per-phoneId snapshot of privileged packages (names + UIDs). */
+ private List<Pair<List<String>, int[]>> mCarrierPrivilegeStates;
+
/**
* Support backward compatibility for {@link android.telephony.TelephonyDisplayInfo}.
*/
@@ -673,6 +687,7 @@
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
+ mCarrierNetworkChangeState = copyOf(mCarrierNetworkChangeState, mNumPhones);
mIsDataEnabled= copyOf(mIsDataEnabled, mNumPhones);
mDataEnabledReason = copyOf(mDataEnabledReason, mNumPhones);
mAllowedNetworkTypeReason = copyOf(mAllowedNetworkTypeReason, mNumPhones);
@@ -686,6 +701,7 @@
cutListToSize(mBarringInfo, mNumPhones);
cutListToSize(mPhysicalChannelConfigs, mNumPhones);
cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
+ cutListToSize(mCarrierPrivilegeStates, mNumPhones);
return;
}
@@ -718,6 +734,7 @@
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mPreciseDataConnectionStates.add(new ArrayMap<>());
mBarringInfo.add(i, new BarringInfo());
+ mCarrierNetworkChangeState[i] = false;
mTelephonyDisplayInfos[i] = null;
mIsDataEnabled[i] = false;
mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
@@ -725,6 +742,7 @@
mAllowedNetworkTypeReason[i] = -1;
mAllowedNetworkTypeValue[i] = -1;
mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST);
+ mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
}
}
@@ -782,6 +800,7 @@
mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
mBarringInfo = new ArrayList<>();
+ mCarrierNetworkChangeState = new boolean[numPhones];
mTelephonyDisplayInfos = new TelephonyDisplayInfo[numPhones];
mPhysicalChannelConfigs = new ArrayList<>();
mAllowedNetworkTypeReason = new int[numPhones];
@@ -789,6 +808,7 @@
mIsDataEnabled = new boolean[numPhones];
mDataEnabledReason = new int[numPhones];
mLinkCapacityEstimateLists = new ArrayList<>();
+ mCarrierPrivilegeStates = new ArrayList<>();
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
@@ -818,6 +838,7 @@
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mPreciseDataConnectionStates.add(new ArrayMap<>());
mBarringInfo.add(i, new BarringInfo());
+ mCarrierNetworkChangeState[i] = false;
mTelephonyDisplayInfos[i] = null;
mIsDataEnabled[i] = false;
mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
@@ -825,6 +846,7 @@
mAllowedNetworkTypeReason[i] = -1;
mAllowedNetworkTypeValue[i] = -1;
mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST);
+ mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -995,14 +1017,25 @@
}
@Override
- public void listenWithEventList(int subId, String callingPackage, String callingFeatureId,
- IPhoneStateListener callback, int[] events, boolean notifyNow) {
+ public void listenWithEventList(boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess, int subId, String callingPackage,
+ String callingFeatureId, IPhoneStateListener callback,
+ int[] events, boolean notifyNow) {
Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
- listen(callingPackage, callingFeatureId, callback, eventList, notifyNow, subId);
+ listen(renounceFineLocationAccess, renounceFineLocationAccess, callingPackage,
+ callingFeatureId, callback, eventList, notifyNow, subId);
}
private void listen(String callingPackage, @Nullable String callingFeatureId,
IPhoneStateListener callback, Set<Integer> events, boolean notifyNow, int subId) {
+ listen(false, false, callingPackage,
+ callingFeatureId, callback, events, notifyNow, subId);
+ }
+
+ private void listen(boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess, String callingPackage,
+ @Nullable String callingFeatureId, IPhoneStateListener callback,
+ Set<Integer> events, boolean notifyNow, int subId) {
int callerUserId = UserHandle.getCallingUserId();
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
String str = "listen: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid()
@@ -1047,6 +1080,8 @@
r.callback = callback;
r.callingPackage = callingPackage;
r.callingFeatureId = callingFeatureId;
+ r.renounceCoarseLocationAccess = renounceCoarseLocationAccess;
+ r.renounceFineLocationAccess = renounceFineLocationAccess;
r.callerUid = Binder.getCallingUid();
r.callerPid = Binder.getCallingPid();
// Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
@@ -1215,7 +1250,7 @@
}
if (events.contains(TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)) {
try {
- r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
+ r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1709,23 +1744,37 @@
throw new SecurityException("notifyCarrierNetworkChange without carrier privilege");
}
- synchronized (mRecords) {
- mCarrierNetworkChangeState = active;
- for (int subId : subIds) {
- int phoneId = getPhoneIdFromSubId(subId);
+ for (int subId : subIds) {
+ notifyCarrierNetworkChangeWithPermission(subId, active);
+ }
+ }
- if (VDBG) {
- log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
- }
- for (Record r : mRecords) {
- if (r.matchTelephonyCallbackEvent(
- TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)
- && idMatch(r, subId, phoneId)) {
- try {
- r.callback.onCarrierNetworkChange(active);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
- }
+ @Override
+ public void notifyCarrierNetworkChangeWithSubId(int subId, boolean active) {
+ if (!TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext, subId)) {
+ throw new SecurityException(
+ "notifyCarrierNetworkChange without carrier privilege on subId " + subId);
+ }
+
+ notifyCarrierNetworkChangeWithPermission(subId, active);
+ }
+
+ private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
+ synchronized (mRecords) {
+ int phoneId = getPhoneIdFromSubId(subId);
+ mCarrierNetworkChangeState[phoneId] = active;
+
+ if (VDBG) {
+ log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
+ }
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCarrierNetworkChange(active);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
}
}
}
@@ -1856,11 +1905,12 @@
try {
if (!mConfigurationProvider.isDisplayInfoNrAdvancedSupported(
r.callingPackage, Binder.getCallingUserHandle())) {
- telephonyDisplayInfo =
+ r.callback.onDisplayInfoChanged(
getBackwardCompatibleTelephonyDisplayInfo(
- telephonyDisplayInfo);
+ telephonyDisplayInfo));
+ } else {
+ r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
}
- r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2732,6 +2782,104 @@
}
@Override
+ public void addCarrierPrivilegesListener(
+ int phoneId,
+ ICarrierPrivilegesListener callback,
+ String callingPackage,
+ String callingFeatureId) {
+ int callerUserId = UserHandle.getCallingUserId();
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "addCarrierPrivilegesListener");
+ if (VDBG) {
+ log(
+ "listen carrier privs: E pkg=" + pii(callingPackage) + " phoneId=" + phoneId
+ + " uid=" + Binder.getCallingUid()
+ + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId
+ + " callback=" + callback
+ + " callback.asBinder=" + callback.asBinder());
+ }
+ if (!validatePhoneId(phoneId)) {
+ throw new IllegalArgumentException("Invalid slot index: " + phoneId);
+ }
+
+ synchronized (mRecords) {
+ Record r = add(
+ callback.asBinder(), Binder.getCallingUid(), Binder.getCallingPid(), false);
+
+ if (r == null) return;
+
+ r.context = mContext;
+ r.carrierPrivilegesListener = callback;
+ r.callingPackage = callingPackage;
+ r.callingFeatureId = callingFeatureId;
+ r.callerUid = Binder.getCallingUid();
+ r.callerPid = Binder.getCallingPid();
+ r.phoneId = phoneId;
+ r.eventList = new ArraySet<>();
+ if (DBG) {
+ log("listen carrier privs: Register r=" + r);
+ }
+
+ Pair<List<String>, int[]> state = mCarrierPrivilegeStates.get(phoneId);
+ try {
+ r.carrierPrivilegesListener.onCarrierPrivilegesChanged(
+ Collections.unmodifiableList(state.first),
+ Arrays.copyOf(state.second, state.second.length));
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
+ }
+
+ @Override
+ public void removeCarrierPrivilegesListener(
+ ICarrierPrivilegesListener callback, String callingPackage) {
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "removeCarrierPrivilegesListener");
+ remove(callback.asBinder());
+ }
+
+ @Override
+ public void notifyCarrierPrivilegesChanged(
+ int phoneId, List<String> privilegedPackageNames, int[] privilegedUids) {
+ if (!checkNotifyPermission("notifyCarrierPrivilegesChanged")) {
+ return;
+ }
+ if (!validatePhoneId(phoneId)) return;
+ if (VDBG) {
+ log(
+ "notifyCarrierPrivilegesChanged: phoneId=" + phoneId
+ + ", <packages=" + pii(privilegedPackageNames)
+ + ", uids=" + Arrays.toString(privilegedUids) + ">");
+ }
+ synchronized (mRecords) {
+ mCarrierPrivilegeStates.set(
+ phoneId, new Pair<>(privilegedPackageNames, privilegedUids));
+ for (Record r : mRecords) {
+ // Listeners are per-slot, not per-subscription. This is to provide a stable
+ // view across SIM profile switches.
+ if (!r.matchCarrierPrivilegesListener()
+ || !idMatch(r, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phoneId)) {
+ continue;
+ }
+ try {
+ // Make sure even in-process listeners can't modify the values.
+ r.carrierPrivilegesListener.onCarrierPrivilegesChanged(
+ Collections.unmodifiableList(privilegedPackageNames),
+ Arrays.copyOf(privilegedUids, privilegedUids.length));
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2772,6 +2920,7 @@
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
pw.println("mBarringInfo=" + mBarringInfo.get(i));
+ pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState[i]);
pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]);
pw.println("mIsDataEnabled=" + mIsDataEnabled);
pw.println("mDataEnabledReason=" + mDataEnabledReason);
@@ -2779,9 +2928,14 @@
pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]);
pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i));
pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i));
+ // We need to obfuscate package names, and primitive arrays' native toString is ugly
+ Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
+ pw.println(
+ "mCarrierPrivilegeState=<packages=" + pii(carrierPrivilegeState.first)
+ + ", uids=" + Arrays.toString(carrierPrivilegeState.second) + ">");
pw.decreaseIndent();
}
- pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
+
pw.println("mPhoneCapability=" + mPhoneCapability);
pw.println("mActiveDataSubId=" + mActiveDataSubId);
pw.println("mRadioPowerState=" + mRadioPowerState);
@@ -3198,6 +3352,9 @@
* If you don't need app compat logic, use {@link #checkFineLocationAccess(Record)}.
*/
private boolean checkFineLocationAccess(Record r, int minSdk) {
+ if (r.renounceFineLocationAccess) {
+ return false;
+ }
LocationAccessPolicy.LocationPermissionQuery query =
new LocationAccessPolicy.LocationPermissionQuery.Builder()
.setCallingPackage(r.callingPackage)
@@ -3224,6 +3381,9 @@
* If you don't need app compat logic, use {@link #checkCoarseLocationAccess(Record)}.
*/
private boolean checkCoarseLocationAccess(Record r, int minSdk) {
+ if (r.renounceCoarseLocationAccess) {
+ return false;
+ }
LocationAccessPolicy.LocationPermissionQuery query =
new LocationAccessPolicy.LocationPermissionQuery.Builder()
.setCallingPackage(r.callingPackage)
@@ -3499,4 +3659,10 @@
private static String pii(String packageName) {
return Build.IS_DEBUGGABLE ? packageName : "***";
}
+
+ /** Redacts an entire list of package names if necessary. */
+ private static String pii(List<String> packageNames) {
+ if (packageNames.isEmpty() || Build.IS_DEBUGGABLE) return packageNames.toString();
+ return "[***, size=" + packageNames.size() + "]";
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3f816c88..dd5ba6e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -91,6 +91,7 @@
import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -300,6 +301,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -7743,6 +7745,17 @@
} else {
killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
"Too many Binders sent to SYSTEM");
+ // We need to run a GC here, because killing the processes involved
+ // actually isn't guaranteed to free up the proxies; in fact, if the
+ // GC doesn't run for a long time, we may even exceed the global
+ // proxy limit for a process (20000), resulting in system_server itself
+ // being killed.
+ // Note that the GC here might not actually clean up all the proxies,
+ // because the binder reference decrements will come in asynchronously;
+ // but if new processes belonging to the UID keep adding proxies, we
+ // will get another callback here, and run the GC again - this time
+ // cleaning up the old proxies.
+ VMRuntime.getRuntime().requestConcurrentGC();
}
}, mHandler);
t.traceEnd(); // setBinderProxies
@@ -14246,6 +14259,8 @@
private void checkExcessivePowerUsage() {
updateCpuStatsNow();
+ final boolean monitorPhantomProcs = mSystemReady && FeatureFlagUtils.isEnabled(mContext,
+ SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
synchronized (mProcLock) {
final boolean doCpuKills = mLastPowerCheckUptime != 0;
final long curUptime = SystemClock.uptimeMillis();
@@ -14271,9 +14286,11 @@
updateAppProcessCpuTimeLPr(uptimeSince, doCpuKills, checkDur, cpuLimit, app);
- // Also check the phantom processes if there is any
- updatePhantomProcessCpuTimeLPr(
- uptimeSince, doCpuKills, checkDur, cpuLimit, app);
+ if (monitorPhantomProcs) {
+ // Also check the phantom processes if there is any
+ updatePhantomProcessCpuTimeLPr(
+ uptimeSince, doCpuKills, checkDur, cpuLimit, app);
+ }
}
});
}
@@ -15413,12 +15430,14 @@
}
@Override
- public void updateDeviceIdleTempAllowlist(int[] appids, int changingUid, boolean adding,
- long durationMs, @TempAllowListType int type, @ReasonCode int reasonCode,
- @Nullable String reason, int callingUid) {
+ public void updateDeviceIdleTempAllowlist(@Nullable int[] appids, int changingUid,
+ boolean adding, long durationMs, @TempAllowListType int type,
+ @ReasonCode int reasonCode, @Nullable String reason, int callingUid) {
synchronized (ActivityManagerService.this) {
synchronized (mProcLock) {
- mDeviceIdleTempAllowlist = appids;
+ if (appids != null) {
+ mDeviceIdleTempAllowlist = appids;
+ }
if (adding) {
if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
// Note, the device idle temp-allowlist are by app-ids, but here
@@ -15428,12 +15447,7 @@
callingUid));
}
} else {
- // Note in the removing case, we need to remove all the UIDs matching
- // the appId, because DeviceIdle's temp-allowlist are based on AppIds,
- // not UIDs.
- // For eacmple, "cmd deviceidle tempallowlist -r PACKAGE" will
- // not only remove this app for user 0, but for all users.
- mFgsStartTempAllowList.removeAppId(UserHandle.getAppId(changingUid));
+ mFgsStartTempAllowList.removeUid(changingUid);
}
setAppIdTempAllowlistStateLSP(changingUid, adding);
}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 36c0de9..1496b30 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
@@ -77,6 +78,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DebugUtils;
+import android.util.FeatureFlagUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -1792,6 +1794,8 @@
}
void updateCpuStatsNow() {
+ final boolean monitorPhantomProcs = mService.mSystemReady && FeatureFlagUtils.isEnabled(
+ mService.mContext, SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
synchronized (mProcessCpuTracker) {
mProcessCpuMutexFree.set(false);
final long now = SystemClock.uptimeMillis();
@@ -1830,7 +1834,7 @@
}
}
- if (haveNewCpuStats) {
+ if (monitorPhantomProcs && haveNewCpuStats) {
mService.mPhantomProcessList.updateProcessCpuStatesLocked(mProcessCpuTracker);
}
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 0c633ca..7ba032f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -478,7 +478,7 @@
for (int uid : uidsToRemove) {
FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, uid,
FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED);
- mStats.removeIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
+ mStats.maybeRemoveIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis());
}
mStats.clearPendingRemovedUids();
diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java
index 50278fd..e6ffcfc 100644
--- a/services/core/java/com/android/server/am/CacheOomRanker.java
+++ b/services/core/java/com/android/server/am/CacheOomRanker.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import android.os.Process;
+import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.util.Slog;
@@ -38,21 +40,40 @@
private static final boolean DEFAULT_USE_OOM_RE_RANKING = false;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK = "oom_re_ranking_number_to_re_rank";
- @VisibleForTesting static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8;
+ @VisibleForTesting
+ static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS =
+ "oom_re_ranking_preserve_top_n_apps";
+ @VisibleForTesting
+ static final int DEFAULT_PRESERVE_TOP_N_APPS = 3;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_USE_FREQUENT_RSS = "oom_re_ranking_rss_use_frequent_rss";
+ @VisibleForTesting
+ static final boolean DEFAULT_USE_FREQUENT_RSS = true;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS = "oom_re_ranking_rss_update_rate_ms";
+ @VisibleForTesting
+ static final long DEFAULT_RSS_UPDATE_RATE_MS = 10_000; // 10 seconds
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_USES_WEIGHT = "oom_re_ranking_uses_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_RSS_WEIGHT = "oom_re_ranking_rss_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f;
private static final Comparator<RankedProcessRecord> SCORED_PROCESS_RECORD_COMPARATOR =
new ScoreComparator();
private static final Comparator<RankedProcessRecord> CACHE_USE_COMPARATOR =
new CacheUseComparator();
+ private static final Comparator<RankedProcessRecord> RSS_COMPARATOR =
+ new RssComparator();
private static final Comparator<RankedProcessRecord> LAST_RSS_COMPARATOR =
new LastRssComparator();
private static final Comparator<RankedProcessRecord> LAST_ACTIVITY_TIME_COMPARATOR =
@@ -61,20 +82,33 @@
private final Object mPhenotypeFlagLock = new Object();
private final ActivityManagerService mService;
+ private final ProcessDependencies mProcessDependencies;
private final ActivityManagerGlobalLock mProcLock;
private final Object mProfilerLock;
@GuardedBy("mPhenotypeFlagLock")
private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ int mPreserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ boolean mUseFrequentRss = DEFAULT_USE_FREQUENT_RSS;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ long mRssUpdateRateMs = DEFAULT_RSS_UPDATE_RATE_MS;
// Weight to apply to the LRU ordering.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT;
+ @VisibleForTesting
+ float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT;
// Weight to apply to the ordering by number of times the process has been added to the cache.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT;
+ @VisibleForTesting
+ float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT;
// Weight to apply to the ordering by RSS used by the processes.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT;
+ @VisibleForTesting
+ float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT;
// Positions to replace in the lru list.
@GuardedBy("mPhenotypeFlagLock")
@@ -93,6 +127,12 @@
updateUseOomReranking();
} else if (KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK.equals(name)) {
updateNumberToReRank();
+ } else if (KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS.equals(name)) {
+ updatePreserveTopNApps();
+ } else if (KEY_OOM_RE_RANKING_USE_FREQUENT_RSS.equals(name)) {
+ updateUseFrequentRss();
+ } else if (KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS.equals(name)) {
+ updateRssUpdateRateMs();
} else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) {
updateLruWeight();
} else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) {
@@ -106,9 +146,15 @@
};
CacheOomRanker(final ActivityManagerService service) {
+ this(service, new ProcessDependenciesImpl());
+ }
+
+ @VisibleForTesting
+ CacheOomRanker(final ActivityManagerService service, ProcessDependencies processDependencies) {
mService = service;
mProcLock = service.mProcLock;
mProfilerLock = service.mAppProfiler.mProfilerLock;
+ mProcessDependencies = processDependencies;
}
/** Load settings from device config and register a listener for changes. */
@@ -160,6 +206,31 @@
}
@GuardedBy("mPhenotypeFlagLock")
+ private void updatePreserveTopNApps() {
+ int preserveTopNApps = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, DEFAULT_PRESERVE_TOP_N_APPS);
+ if (preserveTopNApps < 0) {
+ Slog.w(OomAdjuster.TAG,
+ "Found negative value for preserveTopNApps, setting to default: "
+ + preserveTopNApps);
+ preserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS;
+ }
+ mPreserveTopNApps = preserveTopNApps;
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateRssUpdateRateMs() {
+ mRssUpdateRateMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS, DEFAULT_RSS_UPDATE_RATE_MS);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateUseFrequentRss() {
+ mUseFrequentRss = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_USE_FREQUENT_RSS, DEFAULT_USE_FREQUENT_RSS);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
private void updateLruWeight() {
mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_OOM_RE_RANKING_LRU_WEIGHT, DEFAULT_OOM_RE_RANKING_LRU_WEIGHT);
@@ -183,9 +254,39 @@
*/
@GuardedBy({"mService", "mProcLock"})
void reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart) {
+ // The lruList is a list of processes ordered by how recently they were used. The
+ // least-recently-used apps are at the beginning of the list. We keep track of two
+ // indices in the lruList:
+ //
+ // getNumberToReRank=5, preserveTopNApps=3, lruProcessServiceStart=7,
+ // lruList=
+ // 0: app A ^
+ // 1: app B | These apps are re-ranked, as they are the first five apps (see
+ // 2: app C | getNumberToReRank), excluding...
+ // 3: app D v
+ // 4: app E ^
+ // 5: app F | The three most-recently-used apps in the cache (see preserveTopNApps).
+ // 6: app G v
+ // 7: service A ^
+ // 8: service B | Everything beyond lruProcessServiceStart is ignored, as these aren't
+ // 9: service C | apps.
+ // 10: activity A |
+ // ... |
+ //
+ // `numProcessesEvaluated` moves across the apps (indices 0-6) or until we've found enough
+ // apps to re-rank, and made sure none of them are in the top `preserveTopNApps` apps.
+ // Re-ranked apps are copied into `scoredProcessRecords`, where the re-ranking calculation
+ // happens.
+ //
+ // Note that some apps in the `lruList` can be skipped, if they don't pass
+ //`appCanBeReRanked`.
+
float lruWeight;
float usesWeight;
float rssWeight;
+ int preserveTopNApps;
+ boolean useFrequentRss;
+ long rssUpdateRateMs;
int[] lruPositions;
RankedProcessRecord[] scoredProcessRecords;
@@ -193,6 +294,9 @@
lruWeight = mLruWeight;
usesWeight = mUsesWeight;
rssWeight = mRssWeight;
+ preserveTopNApps = mPreserveTopNApps;
+ useFrequentRss = mUseFrequentRss;
+ rssUpdateRateMs = mRssUpdateRateMs;
lruPositions = mLruPositions;
scoredProcessRecords = mScoredProcessRecords;
}
@@ -202,52 +306,98 @@
return;
}
+ int numProcessesEvaluated = 0;
// Collect the least recently used processes to re-rank, only rank cached
// processes further down the list than mLruProcessServiceStart.
- int cachedProcessPos = 0;
- for (int i = 0; i < lruProcessServiceStart
- && cachedProcessPos < scoredProcessRecords.length; ++i) {
- ProcessRecord app = lruList.get(i);
+ int numProcessesReRanked = 0;
+ while (numProcessesEvaluated < lruProcessServiceStart
+ && numProcessesReRanked < scoredProcessRecords.length) {
+ ProcessRecord process = lruList.get(numProcessesEvaluated);
// Processes that will be assigned a cached oom adj score.
- if (!app.isKilledByAm() && app.getThread() != null && app.mState.getCurAdj()
- >= ProcessList.UNKNOWN_ADJ) {
- scoredProcessRecords[cachedProcessPos].proc = app;
- scoredProcessRecords[cachedProcessPos].score = 0.0f;
- lruPositions[cachedProcessPos] = i;
- ++cachedProcessPos;
+ if (appCanBeReRanked(process)) {
+ scoredProcessRecords[numProcessesReRanked].proc = process;
+ scoredProcessRecords[numProcessesReRanked].score = 0.0f;
+ lruPositions[numProcessesReRanked] = numProcessesEvaluated;
+ ++numProcessesReRanked;
+ }
+ ++numProcessesEvaluated;
+ }
+
+ // Count how many apps we're not re-ranking (up to preserveTopNApps).
+ int numProcessesNotReRanked = 0;
+ while (numProcessesEvaluated < lruProcessServiceStart
+ && numProcessesNotReRanked < preserveTopNApps) {
+ ProcessRecord process = lruList.get(numProcessesEvaluated);
+ if (appCanBeReRanked(process)) {
+ numProcessesNotReRanked++;
+ }
+ numProcessesEvaluated++;
+ }
+ // Exclude the top `preserveTopNApps` apps from re-ranking.
+ if (numProcessesNotReRanked < preserveTopNApps) {
+ numProcessesReRanked -= preserveTopNApps - numProcessesNotReRanked;
+ if (numProcessesReRanked < 0) {
+ numProcessesReRanked = 0;
}
}
- // TODO maybe ensure a certain number above this in the cache before re-ranking.
- if (cachedProcessPos < scoredProcessRecords.length) {
- // Ignore we don't have enough processes to worry about re-ranking.
- return;
+ if (useFrequentRss) {
+ // Update RSS values for re-ranked apps.
+ long nowMs = SystemClock.elapsedRealtime();
+ for (int i = 0; i < numProcessesReRanked; ++i) {
+ RankedProcessRecord scoredProcessRecord = scoredProcessRecords[i];
+ long sinceUpdateMs =
+ nowMs - scoredProcessRecord.proc.mState.getCacheOomRankerRssTimeMs();
+ if (scoredProcessRecord.proc.mState.getCacheOomRankerRss() != 0
+ && sinceUpdateMs < rssUpdateRateMs) {
+ continue;
+ }
+
+ long[] rss = mProcessDependencies.getRss(scoredProcessRecord.proc.getPid());
+ if (rss == null || rss.length == 0) {
+ Slog.e(
+ OomAdjuster.TAG,
+ "Process.getRss returned bad value, not re-ranking: "
+ + Arrays.toString(rss));
+ return;
+ }
+ // First element is total RSS:
+ // frameworks/base/core/jni/android_util_Process.cpp:1192
+ scoredProcessRecord.proc.mState.setCacheOomRankerRss(rss[0], nowMs);
+ scoredProcessRecord.proc.mProfile.setLastRss(rss[0]);
+ }
}
// Add scores for each of the weighted features we want to rank based on.
if (lruWeight > 0.0f) {
// This doesn't use the LRU list ordering as after the first re-ranking
// that will no longer be lru.
- Arrays.sort(scoredProcessRecords, LAST_ACTIVITY_TIME_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked,
+ LAST_ACTIVITY_TIME_COMPARATOR);
addToScore(scoredProcessRecords, lruWeight);
}
if (rssWeight > 0.0f) {
- synchronized (mService.mAppProfiler.mProfilerLock) {
- Arrays.sort(scoredProcessRecords, LAST_RSS_COMPARATOR);
+ if (useFrequentRss) {
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, RSS_COMPARATOR);
+ } else {
+ synchronized (mService.mAppProfiler.mProfilerLock) {
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, LAST_RSS_COMPARATOR);
+ }
}
addToScore(scoredProcessRecords, rssWeight);
}
if (usesWeight > 0.0f) {
- Arrays.sort(scoredProcessRecords, CACHE_USE_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, CACHE_USE_COMPARATOR);
addToScore(scoredProcessRecords, usesWeight);
}
// Re-rank by the new combined score.
- Arrays.sort(scoredProcessRecords, SCORED_PROCESS_RECORD_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked,
+ SCORED_PROCESS_RECORD_COMPARATOR);
if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
boolean printedHeader = false;
- for (int i = 0; i < scoredProcessRecords.length; ++i) {
+ for (int i = 0; i < numProcessesReRanked; ++i) {
if (scoredProcessRecords[i].proc.getPid()
!= lruList.get(lruPositions[i]).getPid()) {
if (!printedHeader) {
@@ -260,12 +410,18 @@
}
}
- for (int i = 0; i < scoredProcessRecords.length; ++i) {
+ for (int i = 0; i < numProcessesReRanked; ++i) {
lruList.set(lruPositions[i], scoredProcessRecords[i].proc);
scoredProcessRecords[i].proc = null;
}
}
+ private static boolean appCanBeReRanked(ProcessRecord process) {
+ return !process.isKilledByAm()
+ && process.getThread() != null
+ && process.mState.getCurAdj() >= ProcessList.UNKNOWN_ADJ;
+ }
+
private static void addToScore(RankedProcessRecord[] scores, float weight) {
for (int i = 1; i < scores.length; ++i) {
scores[i].score += i * weight;
@@ -305,6 +461,16 @@
}
}
+ private static class RssComparator implements Comparator<RankedProcessRecord> {
+ @Override
+ public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
+ // High RSS first to match least recently used.
+ return Long.compare(
+ o2.proc.mState.getCacheOomRankerRss(),
+ o1.proc.mState.getCacheOomRankerRss());
+ }
+ }
+
private static class LastRssComparator implements Comparator<RankedProcessRecord> {
@Override
public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
@@ -317,4 +483,18 @@
public ProcessRecord proc;
public float score;
}
+
+ /**
+ * Interface for mocking {@link Process} static methods.
+ */
+ interface ProcessDependencies {
+ long[] getRss(int pid);
+ }
+
+ private static class ProcessDependenciesImpl implements ProcessDependencies {
+ @Override
+ public long[] getRss(int pid) {
+ return Process.getRss(pid);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f32aa22..6230919 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -157,6 +157,9 @@
static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;
+ // Bitfield values for sync transactions received by frozen binder threads
+ static final int TXNS_PENDING_WHILE_FROZEN = 4;
+
/**
* This thread must be moved to the system background cpuset.
* If that doesn't happen, it's probably going to draw a lot of power.
@@ -611,8 +614,9 @@
* binder for the specificed pid.
*
* @throws RuntimeException in case a flush/freeze operation could not complete successfully.
+ * @return 0 if success, or -EAGAIN indicating there's pending transaction.
*/
- private static native void freezeBinder(int pid, boolean freeze);
+ private static native int freezeBinder(int pid, boolean freeze);
/**
* Retrieves binder freeze info about a process.
@@ -641,21 +645,25 @@
char state = (char) fr.read();
if (state == '1' || state == '0') {
+ // Also check freezer binder ioctl
+ getBinderFreezeInfo(Process.myPid());
supported = true;
} else {
Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
}
} catch (java.io.FileNotFoundException e) {
- Slog.d(TAG_AM, "cgroup.freeze not present");
+ Slog.w(TAG_AM, "cgroup.freeze not present");
+ } catch (RuntimeException e) {
+ Slog.w(TAG_AM, "unable to read freezer info");
} catch (Exception e) {
- Slog.d(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
+ Slog.w(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
}
if (fr != null) {
try {
fr.close();
} catch (java.io.IOException e) {
- Slog.e(TAG_AM, "Exception closing freezer.killable: " + e.toString());
+ Slog.e(TAG_AM, "Exception closing cgroup.freeze: " + e.toString());
}
}
@@ -948,7 +956,7 @@
int freezeInfo = getBinderFreezeInfo(pid);
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
- Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+ Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received sync transactions while frozen, killing");
app.killLocked("Sync transaction while in frozen state",
ApplicationExitInfo.REASON_OTHER,
@@ -956,8 +964,8 @@
processKilled = true;
}
- if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
- Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+ if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
+ Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received async transactions while frozen");
}
} catch (Exception e) {
@@ -1292,7 +1300,9 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_FROZEN_PROCESS_MSG:
- freezeProcess((ProcessRecord) msg.obj);
+ synchronized (mAm) {
+ freezeProcess((ProcessRecord) msg.obj);
+ }
break;
case REPORT_UNFREEZE_MSG:
int pid = msg.arg1;
@@ -1306,6 +1316,15 @@
}
}
+ @GuardedBy({"mAm", "mProcLock"})
+ private void rescheduleFreeze(final ProcessRecord proc, final String reason) {
+ Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
+ + " " + proc.processName + " (" + reason + ")");
+ unfreezeAppLSP(proc);
+ freezeAppAsyncLSP(proc);
+ }
+
+ @GuardedBy({"mAm"})
private void freezeProcess(final ProcessRecord proc) {
int pid = proc.getPid(); // Unlocked intentionally
final String name = proc.processName;
@@ -1355,10 +1374,15 @@
return;
}
+ Slog.d(TAG_AM, "freezing " + pid + " " + name);
+
// Freeze binder interface before the process, to flush any
// transactions that might be pending.
try {
- freezeBinder(pid, true);
+ if (freezeBinder(pid, true) != 0) {
+ rescheduleFreeze(proc, "outstanding txns");
+ return;
+ }
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
mFreezeHandler.post(() -> {
@@ -1404,24 +1428,36 @@
try {
// post-check to prevent races
+ int freezeInfo = getBinderFreezeInfo(pid);
+
+ if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
+ synchronized (mProcLock) {
+ rescheduleFreeze(proc, "new pending txns");
+ }
+ return;
+ }
+ } catch (RuntimeException e) {
+ Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
+ mFreezeHandler.post(() -> {
+ synchronized (mAm) {
+ proc.killLocked("Unable to freeze binder interface",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
+ }
+ });
+ }
+
+ try {
+ // post-check to prevent races
if (mProcLocksReader.hasFileLocks(pid)) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
}
-
- synchronized (mAm) {
- synchronized (mProcLock) {
- unfreezeAppLSP(proc);
- }
- }
+ unfreezeAppLSP(proc);
}
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
- synchronized (mAm) {
- synchronized (mProcLock) {
- unfreezeAppLSP(proc);
- }
- }
+ unfreezeAppLSP(proc);
}
}
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 5c9d385..2e3e635 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -94,8 +94,6 @@
sGlobalSettingToTypeMap.put(
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
sGlobalSettingToTypeMap.put(
- Settings.Global.ANGLE_ALLOWLIST, String.class);
- sGlobalSettingToTypeMap.put(
Settings.Global.ANGLE_EGL_FEATURES, String.class);
sGlobalSettingToTypeMap.put(
Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5c19ceb..794b149 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,6 +72,7 @@
import static com.android.server.am.ProcessList.TAG_PROCESS_OBSERVERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -114,6 +115,8 @@
import com.android.server.wm.WindowProcessController;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -171,6 +174,27 @@
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
+ static final int CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY = 0;
+ static final int CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY = 1;
+ static final int CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME = 2;
+
+ @IntDef(prefix = { "CACHED_COMPAT_CHANGE_" }, value = {
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY,
+ CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY,
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ static @interface CachedCompatChangeId{}
+
+ /**
+ * Mapping from CACHED_COMPAT_CHANGE_* to the actual compat change id.
+ */
+ static final long[] CACHED_COMPAT_CHANGE_IDS_MAPPING = new long[] {
+ PROCESS_CAPABILITY_CHANGE_ID,
+ CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
+ USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+ };
+
/**
* For some direct access we need to power manager.
*/
@@ -368,6 +392,12 @@
return mPlatformCompatCache;
}
+ boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, ApplicationInfo app,
+ boolean defaultValue) {
+ return getPlatformCompatCache().isChangeEnabled(
+ CACHED_COMPAT_CHANGE_IDS_MAPPING[cachedCompatChangeId], app, defaultValue);
+ }
+
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
this(service, processList, activeUids, createAdjusterThread());
}
@@ -1929,12 +1959,8 @@
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
- boolean enabled = false;
- try {
- enabled = getPlatformCompatCache().isChangeEnabled(
- CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID, s.appInfo);
- } catch (RemoteException e) {
- }
+ final boolean enabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY);
if (enabled) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
@@ -2151,12 +2177,8 @@
// to client's state.
clientProcState = PROCESS_STATE_BOUND_TOP;
state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
- boolean enabled = false;
- try {
- enabled = getPlatformCompatCache().isChangeEnabled(
- PROCESS_CAPABILITY_CHANGE_ID, client.info);
- } catch (RemoteException e) {
- }
+ final boolean enabled = cstate.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
if (enabled) {
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
// TOP process passes all capabilities to the service.
@@ -2800,8 +2822,8 @@
state.setProcStateChanged(true);
}
} else if (state.hasReportedInteraction()) {
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
final long interactionThreshold = fgsInteractionChangeEnabled
? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
: mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
@@ -2811,8 +2833,8 @@
maybeUpdateUsageStatsLSP(app, nowElapsed);
}
} else {
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
final long interactionThreshold = fgsInteractionChangeEnabled
? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
: mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
@@ -2850,29 +2872,18 @@
void setAttachingSchedGroupLSP(ProcessRecord app) {
int initialSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
final ProcessStateRecord state = app.mState;
- // If the process has been marked as foreground via Zygote.START_FLAG_USE_TOP_APP_PRIORITY,
- // then verify that the top priority is actually is applied.
+ // If the process has been marked as foreground, it is starting as the top app (with
+ // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
if (state.hasForegroundActivities()) {
- String fallbackReason = null;
try {
// The priority must be the same as how does {@link #applyOomAdjLSP} set for
// {@link ProcessList.SCHED_GROUP_TOP_APP}. We don't check render thread because it
// is not ready when attaching.
- if (Process.getProcessGroup(app.getPid()) == THREAD_GROUP_TOP_APP) {
- app.getWindowProcessController().onTopProcChanged();
- setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
- } else {
- fallbackReason = "not expected top priority";
- }
- } catch (Exception e) {
- fallbackReason = e.toString();
- }
- if (fallbackReason == null) {
+ app.getWindowProcessController().onTopProcChanged();
+ setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
initialSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;
- } else {
- // The real scheduling group will depend on if there is any component of the process
- // did something during attaching.
- Slog.w(TAG, "Fallback pre-set sched group to default: " + fallbackReason);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e);
}
}
@@ -2901,8 +2912,8 @@
if (mService.mUsageStatsService == null) {
return;
}
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
boolean isInteraction;
// To avoid some abuse patterns, we are going to be careful about what we consider
// to be an app interaction. Being the top activity doesn't count while the display
diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md
index eda511a..febc37b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.md
+++ b/services/core/java/com/android/server/am/OomAdjuster.md
@@ -59,6 +59,7 @@
* The next two factors are either it was the previous process with visible UI to the user, or it's a backup agent.
* And then it goes to the massive searches against the service connections and the content providers, each of the clients will be evaluated, and the Oom Adj score could get updated according to its clients' scores. However there are a bunch of service binding flags which could impact the result:
* Below table captures the results with given various service binding states:
+
| Conditon #1 | Condition #2 | Condition #3 | Condition #4 | Result |
|---------------------------------|------------------------------------------------------------|----------------------------------------------|---------------------------------------------------|--------------------------|
| `BIND_WAIVE_PRIORITY` not set | `BIND_ALLOW_OOM_MANAGEMENT` set | Shown UI && Not Home | | Use the app's own Adj |
@@ -83,6 +84,7 @@
| | | `BIND_NOT_FOREGROUND` not set | `BIND_IMPORTANT` is set | Sched = top app bound |
| | | | `BIND_IMPORTANT` is NOT set | Sched = default |
* Below table captures the results with given various content provider binding states:
+
| Conditon #1 | Condition #2 | Condition #3 | Result |
|---------------------------------|------------------------------------------------------------|----------------------------------------------|--------------------------|
| Client's process state >= cached| | | Client ProcState = empty |
@@ -95,6 +97,7 @@
| Still within retain time | Adj > previous app Adj | | adj = previuos app adj |
| | Process state > last activity | | ProcState = last activity|
* Some additional tweaks after the above ones:
+
| Conditon #1 | Condition #2 | Condition #3 | Result |
|---------------------------------|------------------------------------------------------------|----------------------------------------------|------------------------------------|
| Process state >= cached empty | Has client activities | | ProcState = cached activity client |
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index b07684c..2ec1aed 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -18,6 +18,7 @@
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -28,6 +29,7 @@
import android.os.Handler;
import android.os.Process;
import android.os.StrictMode;
+import android.util.FeatureFlagUtils;
import android.util.Slog;
import android.util.SparseArray;
@@ -419,6 +421,10 @@
* order of the oom adjs of their parent process.
*/
void trimPhantomProcessesIfNecessary() {
+ if (!mService.mSystemReady || !FeatureFlagUtils.isEnabled(mService.mContext,
+ SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS)) {
+ return;
+ }
synchronized (mService.mProcLock) {
synchronized (mLock) {
mTrimPhantomProcessScheduled = false;
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index dc6bcd8..206dd88 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -21,6 +21,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.OomAdjuster.CachedCompatChangeId;
import static com.android.server.am.ProcessRecord.TAG;
import android.annotation.ElapsedRealtimeLong;
@@ -342,6 +343,20 @@
private int mCacheOomRankerUseCount;
/**
+ * Process memory usage (RSS).
+ *
+ * Periodically populated by {@code CacheOomRanker}, stored in this object to cache the values.
+ */
+ @GuardedBy("mService")
+ private long mCacheOomRankerRss;
+
+ /**
+ * The last time, in milliseconds since boot, since {@link #mCacheOomRankerRss} was updated.
+ */
+ @GuardedBy("mService")
+ private long mCacheOomRankerRssTimeMs;
+
+ /**
* Whether or not this process is reachable from given process.
*/
@GuardedBy("mService")
@@ -377,6 +392,16 @@
@GuardedBy("mService")
private int mCachedIsReceivingBroadcast = VALUE_INVALID;
+ /**
+ * Cache the return value of PlatformCompat.isChangeEnabled().
+ */
+ @GuardedBy("mService")
+ private int[] mCachedCompatChanges = new int[] {
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME
+ };
+
@GuardedBy("mService")
private int mCachedAdj = ProcessList.INVALID_ADJ;
@GuardedBy("mService")
@@ -566,6 +591,10 @@
@GuardedBy({"mService", "mProcLock"})
void setSetProcState(int setProcState) {
+ if (ActivityManager.isProcStateCached(mSetProcState)
+ && !ActivityManager.isProcStateCached(setProcState)) {
+ mCacheOomRankerUseCount++;
+ }
mSetProcState = setProcState;
}
@@ -829,12 +858,7 @@
@GuardedBy("mService")
void setCached(boolean cached) {
- if (mCached != cached) {
- mCached = cached;
- if (cached) {
- ++mCacheOomRankerUseCount;
- }
- }
+ mCached = cached;
}
@GuardedBy("mService")
@@ -1009,6 +1033,16 @@
}
@GuardedBy("mService")
+ boolean getCachedCompatChange(@CachedCompatChangeId int cachedCompatChangeId) {
+ if (mCachedCompatChanges[cachedCompatChangeId] == VALUE_INVALID) {
+ mCachedCompatChanges[cachedCompatChangeId] = mService.mOomAdjuster
+ .isChangeEnabled(cachedCompatChangeId, mApp.info, false /* default */)
+ ? VALUE_TRUE : VALUE_FALSE;
+ }
+ return mCachedCompatChanges[cachedCompatChangeId] == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
int adj, boolean foregroundActivities, boolean hasVisibleActivities, int procState,
int schedGroup, int appUid, int logUid, int processCurTop) {
@@ -1088,6 +1122,9 @@
mCurSchedGroup = mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
mCurProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
PROCESS_STATE_NONEXISTENT;
+ for (int i = 0; i < mCachedCompatChanges.length; i++) {
+ mCachedCompatChanges[i] = VALUE_INVALID;
+ }
}
@GuardedBy("mService")
@@ -1127,6 +1164,21 @@
return mLastInvisibleTime;
}
+ public void setCacheOomRankerRss(long rss, long rssTimeMs) {
+ mCacheOomRankerRss = rss;
+ mCacheOomRankerRssTimeMs = rssTimeMs;
+ }
+
+ @GuardedBy("mService")
+ public long getCacheOomRankerRss() {
+ return mCacheOomRankerRss;
+ }
+
+ @GuardedBy("mService")
+ public long getCacheOomRankerRssTimeMs() {
+ return mCacheOomRankerRssTimeMs;
+ }
+
@GuardedBy({"mService", "mProcLock"})
void dump(PrintWriter pw, String prefix, long nowUptime) {
if (mReportedInteraction || mFgInteractionTime != 0) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 804e442..17930ea 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -927,9 +927,9 @@
}
public void postNotification() {
- final int appUid = appInfo.uid;
- final int appPid = app.getPid();
- if (isForeground && foregroundNoti != null) {
+ if (isForeground && foregroundNoti != null && app != null) {
+ final int appUid = appInfo.uid;
+ final int appPid = app.getPid();
// Do asynchronous communication with notification manager to
// avoid deadlocks.
final String localPackageName = packageName;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index af8d7a6..3003c52 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -272,6 +272,13 @@
"com.android.graphics.intervention.wm.allowDownscale";
/**
+ * Metadata that can be included in the app manifest to allow/disallow any ANGLE
+ * interventions. Default value is TRUE.
+ */
+ public static final String METADATA_ANGLE_ALLOW_ANGLE =
+ "com.android.graphics.intervention.angle.allowAngle";
+
+ /**
* Metadata that needs to be included in the app manifest to OPT-IN to PERFORMANCE mode.
* This means the app will assume full responsibility for the experience provided by this
* mode and the system will enable no window manager downscaling.
@@ -294,6 +301,7 @@
private boolean mPerfModeOptedIn;
private boolean mBatteryModeOptedIn;
private boolean mAllowDownscale;
+ private boolean mAllowAngle;
GamePackageConfiguration(String packageName, int userId) {
mPackageName = packageName;
@@ -305,10 +313,12 @@
mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
+ mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
} else {
mPerfModeOptedIn = false;
mBatteryModeOptedIn = false;
mAllowDownscale = true;
+ mAllowAngle = true;
}
} catch (PackageManager.NameNotFoundException e) {
// Not all packages are installed, hence ignore those that are not installed yet.
@@ -340,14 +350,26 @@
public static final String MODE_KEY = "mode";
public static final String SCALING_KEY = "downscaleFactor";
public static final String DEFAULT_SCALING = "1.0";
+ public static final String ANGLE_KEY = "useAngle";
private final @GameMode int mGameMode;
private final String mScaling;
+ private final boolean mUseAngle;
GameModeConfiguration(KeyValueListParser parser) {
mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
- mScaling = !mAllowDownscale || isGameModeOptedIn(mGameMode)
+ // isGameModeOptedIn() returns if an app will handle all of the changes necessary
+ // for a particular game mode. If so, the Android framework (i.e.
+ // GameManagerService) will not do anything for the app (like window scaling or
+ // using ANGLE).
+ mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
+ // We only want to use ANGLE if:
+ // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
+ // - The app has not opted in to performing the work itself AND
+ // - The Phenotype config has enabled it.
+ mUseAngle = mAllowAngle && !willGamePerformOptimizations(mGameMode)
+ && parser.getBoolean(ANGLE_KEY, false);
}
public int getGameMode() {
@@ -358,6 +380,10 @@
return mScaling;
}
+ public boolean getUseAngle() {
+ return mUseAngle;
+ }
+
public boolean isValid() {
return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
|| mGameMode == GameManager.GAME_MODE_BATTERY)
@@ -368,7 +394,8 @@
* @hide
*/
public String toString() {
- return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]";
+ return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:"
+ + mUseAngle + "]";
}
/**
@@ -384,13 +411,14 @@
}
/**
- * Gets whether a package has opted into a game mode via its manifest.
+ * Returns if the app will assume full responsibility for the experience provided by this
+ * mode. If True, the system will not perform any interventions for the app.
*
* @return True if the app package has specified in its metadata either:
* "com.android.app.gamemode.performance.enabled" or
* "com.android.app.gamemode.battery.enabled" with a value of "true"
*/
- public boolean isGameModeOptedIn(@GameMode int gameMode) {
+ public boolean willGamePerformOptimizations(@GameMode int gameMode) {
return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
|| (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
}
@@ -631,7 +659,34 @@
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
}
- updateCompatModeDownscale(packageName, gameMode);
+ updateInterventions(packageName, gameMode);
+ }
+
+ /**
+ * Get if ANGLE is enabled for the package for the currently enabled game mode.
+ * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+ */
+ @Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+ public @GameMode boolean getAngleEnabled(String packageName, int userId)
+ throws SecurityException {
+ final int gameMode = getGameMode(packageName, userId);
+ if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+ return false;
+ }
+
+ synchronized (mDeviceConfigLock) {
+ final GamePackageConfiguration config = mConfigs.get(packageName);
+ if (config == null) {
+ return false;
+ }
+ GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
+ config.getGameModeConfiguration(gameMode);
+ if (gameModeConfiguration == null) {
+ return false;
+ }
+ return gameModeConfiguration.getUseAngle();
+ }
}
/**
@@ -753,7 +808,7 @@
if (DEBUG) {
Slog.v(TAG, dumpDeviceConfigs());
}
- if (packageConfig.isGameModeOptedIn(gameMode)) {
+ if (packageConfig.willGamePerformOptimizations(gameMode)) {
disableCompatScale(packageName);
return;
}
@@ -782,6 +837,17 @@
return (bitField & modeToBitmask(gameMode)) != 0;
}
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ private void updateUseAngle(String packageName, @GameMode int gameMode) {
+ // TODO (b/188475576): Nothing to do yet. Remove if it's still empty when we're ready to
+ // ship.
+ }
+
+ private void updateInterventions(String packageName, @GameMode int gameMode) {
+ updateCompatModeDownscale(packageName, gameMode);
+ updateUseAngle(packageName, gameMode);
+ }
+
/**
* @hide
*/
@@ -839,11 +905,11 @@
if (newGameMode != gameMode) {
setGameMode(packageName, newGameMode, userId);
}
- updateCompatModeDownscale(packageName, gameMode);
+ updateInterventions(packageName, gameMode);
}
}
} catch (Exception e) {
- Slog.e(TAG, "Failed to update compat modes for user: " + userId);
+ Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
}
}
@@ -851,7 +917,7 @@
final List<PackageInfo> packages =
mPackageManager.getInstalledPackagesAsUser(0, userId);
return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
- == ApplicationInfo.CATEGORY_GAME)
+ == ApplicationInfo.CATEGORY_GAME)
.map(e -> e.packageName)
.toArray(String[]::new);
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index db2ecc5..19dcee4 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -696,7 +696,7 @@
idpw.print("User Level Hibernation States, ");
idpw.printPair("user", userId);
idpw.println();
- Map<String, UserLevelState> stateMap = mUserStates.get(i);
+ Map<String, UserLevelState> stateMap = mUserStates.get(userId);
idpw.increaseIndent();
for (UserLevelState state : stateMap.values()) {
idpw.print(state);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0b2311b..4fc6614 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1640,10 +1640,10 @@
if (client == null) {
return;
}
- Log.w(TAG, "Speaker client died");
+ Log.w(TAG, "Communication client died");
setCommunicationRouteForClient(
- client.getBinder(), client.getPid(), null,
- BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
+ client.getBinder(), client.getPid(), null, BtHelper.SCO_MODE_UNDEFINED,
+ "onCommunicationRouteClientDied");
}
/**
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 51784bb..e00c8a3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2601,18 +2601,19 @@
case KeyEvent.KEYCODE_VOLUME_UP:
adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, keyEventMode);
+ Binder.getCallingUid(), Binder.getCallingPid(), true, keyEventMode);
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, keyEventMode);
+ Binder.getCallingUid(), Binder.getCallingPid(), true, keyEventMode);
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, VOL_ADJUST_NORMAL);
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ true, VOL_ADJUST_NORMAL);
}
break;
default:
@@ -2625,8 +2626,8 @@
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
- caller, Binder.getCallingUid(), callingHasAudioSettingsPermission(),
- VOL_ADJUST_NORMAL);
+ caller, Binder.getCallingUid(), Binder.getCallingPid(),
+ callingHasAudioSettingsPermission(), VOL_ADJUST_NORMAL);
}
public void setNavigationRepeatSoundEffectsEnabled(boolean enabled) {
@@ -2652,7 +2653,7 @@
}
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
+ String callingPackage, String caller, int uid, int pid, boolean hasModifyAudioSettings,
int keyEventMode) {
if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+ ", flags=" + flags + ", caller=" + caller
@@ -2725,7 +2726,7 @@
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
- adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid,
+ adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, pid,
hasModifyAudioSettings, keyEventMode);
}
@@ -2757,12 +2758,12 @@
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
- Binder.getCallingUid(), callingHasAudioSettingsPermission(),
- VOL_ADJUST_NORMAL);
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ callingHasAudioSettingsPermission(), VOL_ADJUST_NORMAL);
}
protected void adjustStreamVolume(int streamType, int direction, int flags,
- String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
+ String callingPackage, String caller, int uid, int pid, boolean hasModifyAudioSettings,
int keyEventMode) {
if (mUseFixedVolume) {
return;
@@ -2784,8 +2785,7 @@
if (isMuteAdjust &&
(streamType == AudioSystem.STREAM_VOICE_CALL ||
streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_PHONE_STATE)
+ mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
@@ -2795,8 +2795,8 @@
// If the stream is STREAM_ASSISTANT,
// make sure that the calling app have the MODIFY_AUDIO_ROUTING permission.
if (streamType == AudioSystem.STREAM_ASSISTANT &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ mContext.checkPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
@@ -2828,8 +2828,8 @@
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ // validate calling package and app op
+ if (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {
return;
}
@@ -3193,6 +3193,13 @@
}
}
+ private void enforceQueryStatePermission() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing QUERY_AUDIO_STATE permissions");
+ }
+ }
+
private void enforceQueryStateOrModifyRoutingPermission() {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
!= PackageManager.PERMISSION_GRANTED
@@ -3562,8 +3569,7 @@
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {
return;
}
@@ -4001,20 +4007,19 @@
}
private void setMasterMuteInternal(boolean mute, int flags, String callingPackage, int uid,
- int userId) {
+ int userId, int pid) {
// If we are being called by the system check for user we are going to change
// so we handle user restrictions correctly.
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
}
// If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting.
- if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!mute && !checkNoteAppOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)) {
return;
}
if (userId != UserHandle.getCallingUserId() &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
@@ -4054,7 +4059,7 @@
public void setMasterMute(boolean mute, int flags, String callingPackage, int userId) {
enforceModifyAudioRoutingPermission();
setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid(),
- userId);
+ userId, Binder.getCallingPid());
}
/** @see AudioManager#getStreamVolume(int) */
@@ -4096,6 +4101,7 @@
/** Get last audible volume before stream was muted. */
public int getLastAudibleStreamVolume(int streamType) {
+ enforceQueryStatePermission();
ensureValidStreamType(streamType);
int device = getDeviceForStream(streamType);
return (mStreamStates[streamType].getIndex(device) + 5) / 10;
@@ -4140,8 +4146,7 @@
? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE);
// If OP_MUTE_MICROPHONE is set, disallow unmuting.
- if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!on && !checkNoteAppOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "disallow unmuting").record();
return;
}
@@ -4956,8 +4961,8 @@
// direction and stream type swap here because the public
// adjustSuggested has a different order than the other methods.
- adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName, uid,
- hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
+ adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName,
+ uid, pid, hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
}
/** @see AudioManager#adjustStreamVolumeForUid(int, int, int, String, int, int, int) */
@@ -4976,7 +4981,7 @@
.toString()));
}
- adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid,
+ adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid, pid,
hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
}
@@ -5320,6 +5325,10 @@
// TODO investigate internal users due to deprecation of SDK API
/** @see AudioManager#setBluetoothA2dpOn(boolean) */
public void setBluetoothA2dpOn(boolean on) {
+ if (!checkAudioSettingsPermission("setBluetoothA2dpOn()")) {
+ return;
+ }
+
// for logging only
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -5345,6 +5354,10 @@
/** @see AudioManager#startBluetoothSco() */
public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+ if (!checkAudioSettingsPermission("startBluetoothSco()")) {
+ return;
+ }
+
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final int scoAudioMode =
@@ -5367,6 +5380,10 @@
/** @see AudioManager#startBluetoothScoVirtualCall() */
public void startBluetoothScoVirtualCall(IBinder cb) {
+ if (!checkAudioSettingsPermission("startBluetoothScoVirtualCall()")) {
+ return;
+ }
+
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
@@ -10534,4 +10551,31 @@
}
mFullVolumeDevices.remove(audioSystemDeviceOut);
}
+
+ //====================
+ // Helper functions for app ops
+ //====================
+ /**
+ * Validates, and notes an app op for a given uid and package name.
+ * Validation comes from exception catching: a security exception indicates the package
+ * doesn't exist, an IAE indicates the uid and package don't match. The code only checks
+ * if exception was thrown for robustness to code changes in op validation
+ * @param op the app op to check
+ * @param uid the uid of the caller
+ * @param packageName the package to check
+ * @return true if the origin of the call is valid (no uid / package mismatch) and the caller
+ * is allowed to perform the operation
+ */
+ private boolean checkNoteAppOp(int op, int uid, String packageName) {
+ try {
+ if (mAppOps.noteOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error noting op:" + op + " on uid:" + uid + " for package:"
+ + packageName, e);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index bb627e5..00cb280 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -36,7 +36,16 @@
public static final String TAG = "AudioService.FadeOutManager";
+ /** duration of the fade out curve */
/*package*/ static final long FADE_OUT_DURATION_MS = 2000;
+ /**
+ * delay after which a faded out player will be faded back in. This will be heard by the user
+ * only in the case of unmuting players that didn't respect audio focus and didn't stop/pause
+ * when their app lost focus.
+ * This is the amount of time between the app being notified of
+ * the focus loss (when its muted by the fade out), and the time fade in (to unmute) starts
+ */
+ /*package*/ static final long DELAY_FADE_IN_OFFENDERS_MS = 2000;
private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG;
@@ -148,6 +157,11 @@
}
}
+ /**
+ * Remove the app for the given UID from the list of faded out apps, unfade out its players
+ * @param uid the uid for the app to unfade out
+ * @param players map of current available players (so we can get an APC from piid)
+ */
synchronized void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
Log.i(TAG, "unfadeOutUid() uid:" + uid);
final FadedOutApp fa = mFadedApps.remove(uid);
@@ -157,12 +171,6 @@
fa.removeUnfadeAll(players);
}
- synchronized void forgetUid(int uid) {
- //Log.v(TAG, "forget() uid:" + uid);
- //mFadedApps.remove(uid);
- // TODO unfade all players later in case they are reused or the app continued to play
- }
-
// pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
// see {@link PlaybackActivityMonitor#playerEvent}
synchronized void checkFade(@NonNull AudioPlaybackConfiguration apc) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index e6c4abfa..9548ada 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -131,6 +131,11 @@
@Override
public void restoreVShapedPlayers(@NonNull FocusRequester winner) {
mFocusEnforcer.restoreVShapedPlayers(winner);
+ // remove scheduled events to unfade out offending players (if any) corresponding to
+ // this uid, as we're removing any effects of muting/ducking/fade out now
+ mFocusHandler.removeEqualMessages(MSL_L_FORGET_UID,
+ new ForgetFadeUidInfo(winner.getClientUid()));
+
}
@Override
@@ -1182,6 +1187,13 @@
mFocusHandler.obtainMessage(MSG_L_FOCUS_LOSS_AFTER_FADE, focusLoser),
FadeOutManager.FADE_OUT_DURATION_MS);
}
+
+ private void postForgetUidLater(int uid) {
+ mFocusHandler.sendMessageDelayed(
+ mFocusHandler.obtainMessage(MSL_L_FORGET_UID, new ForgetFadeUidInfo(uid)),
+ FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS);
+ }
+
//=================================================================
// Message handling
private Handler mFocusHandler;
@@ -1196,6 +1208,8 @@
*/
private static final int MSG_L_FOCUS_LOSS_AFTER_FADE = 1;
+ private static final int MSL_L_FORGET_UID = 2;
+
private void initFocusThreading() {
mFocusThread = new HandlerThread(TAG);
mFocusThread.start();
@@ -1213,15 +1227,56 @@
if (loser.isInFocusLossLimbo()) {
loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
loser.release();
- mFocusEnforcer.forgetUid(loser.getClientUid());
+ postForgetUidLater(loser.getClientUid());
}
}
break;
+
+ case MSL_L_FORGET_UID:
+ final int uid = ((ForgetFadeUidInfo) msg.obj).mUid;
+ if (DEBUG) {
+ Log.d(TAG, "MSL_L_FORGET_UID uid=" + uid);
+ }
+ mFocusEnforcer.forgetUid(uid);
+ break;
default:
break;
}
}
};
+ }
+ /**
+ * Class to associate a UID with a scheduled event to "forget" a UID for the fade out behavior.
+ * Having a class with an equals() override allows using Handler.removeEqualsMessage() to
+ * unschedule events when needed. Here we need to unschedule the "unfading out" == "forget uid"
+ * whenever a new, more recent, focus related event happens before this one is handled.
+ */
+ private static final class ForgetFadeUidInfo {
+ private final int mUid;
+
+ ForgetFadeUidInfo(int uid) {
+ mUid = uid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ForgetFadeUidInfo f = (ForgetFadeUidInfo) o;
+ if (f.mUid != mUid) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mUid;
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index a13b2eb..55d0728 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -747,7 +747,11 @@
@Override
public void forgetUid(int uid) {
- mFadingManager.forgetUid(uid);
+ final HashMap<Integer, AudioPlaybackConfiguration> players;
+ synchronized (mPlayerLock) {
+ players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone();
+ }
+ mFadingManager.unfadeOutUid(uid, players);
}
//=================================================================
@@ -779,7 +783,7 @@
// the same time if we still have a public client.
while (clientIterator.hasNext()) {
PlayMonitorClient pmc = clientIterator.next();
- if (pcdb.equals(pmc.mDispatcherCb)) {
+ if (pcdb.asBinder().equals(pmc.mDispatcherCb.asBinder())) {
pmc.release();
clientIterator.remove();
} else {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index c97ad55..b2c40d5 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -210,7 +210,7 @@
}
@Override
- public void authenticate(IBinder token, long sessionId, int userId,
+ public long authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo)
throws RemoteException {
// Only allow internal clients to authenticate with a different userId.
@@ -227,18 +227,18 @@
if (!checkAppOps(callingUid, opPackageName, "authenticate()")) {
authenticateFastFail("Denied by app ops: " + opPackageName, receiver);
- return;
+ return -1;
}
if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
authenticateFastFail(
"Unable to authenticate, one or more null arguments", receiver);
- return;
+ return -1;
}
if (!Utils.isForeground(callingUid, callingPid)) {
authenticateFastFail("Caller is not foreground: " + opPackageName, receiver);
- return;
+ return -1;
}
if (promptInfo.containsTestConfigurations()) {
@@ -255,7 +255,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- mBiometricService.authenticate(
+ return mBiometricService.authenticate(
token, sessionId, userId, receiver, opPackageName, promptInfo);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -274,7 +274,7 @@
}
@Override
- public void cancelAuthentication(IBinder token, String opPackageName)
+ public void cancelAuthentication(IBinder token, String opPackageName, long requestId)
throws RemoteException {
checkPermission();
@@ -285,7 +285,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- mBiometricService.cancelAuthentication(token, opPackageName);
+ mBiometricService.cancelAuthentication(token, opPackageName, requestId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index bdde980..0da6a1b 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -128,6 +128,7 @@
@VisibleForTesting final IBinder mToken;
// Info to be shown on BiometricDialog when all cookies are returned.
@VisibleForTesting final PromptInfo mPromptInfo;
+ private final long mRequestId;
private final long mOperationId;
private final int mUserId;
private final IBiometricSensorReceiver mSensorReceiver;
@@ -142,6 +143,8 @@
private @BiometricMultiSensorMode int mMultiSensorMode;
private @MultiSensorState int mMultiSensorState;
private int[] mSensors;
+ // TODO(b/197265902): merge into state
+ private boolean mCancelled;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
private byte[] mTokenEscrow;
@@ -162,6 +165,7 @@
@NonNull ClientDeathReceiver clientDeathReceiver,
@NonNull PreAuthInfo preAuthInfo,
@NonNull IBinder token,
+ long requestId,
long operationId,
int userId,
@NonNull IBiometricSensorReceiver sensorReceiver,
@@ -179,6 +183,7 @@
mClientDeathReceiver = clientDeathReceiver;
mPreAuthInfo = preAuthInfo;
mToken = token;
+ mRequestId = requestId;
mOperationId = operationId;
mUserId = userId;
mSensorReceiver = sensorReceiver;
@@ -187,6 +192,7 @@
mPromptInfo = promptInfo;
mDebugEnabled = debugEnabled;
mFingerprintSensorProperties = fingerprintSensorProperties;
+ mCancelled = false;
try {
mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -233,7 +239,7 @@
Slog.v(TAG, "waiting for cooking for sensor: " + sensor.id);
}
sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
- mUserId, mSensorReceiver, mOpPackageName, cookie,
+ mUserId, mSensorReceiver, mOpPackageName, mRequestId, cookie,
mPromptInfo.isAllowBackgroundAuthentication());
}
}
@@ -255,8 +261,9 @@
true /* credentialAllowed */,
false /* requireConfirmation */,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
} else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
// Some combination of biometric or biometric|credential is requested
@@ -270,6 +277,11 @@
}
void onCookieReceived(int cookie) {
+ if (mCancelled) {
+ Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie);
+ return;
+ }
+
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
sensor.goToStateCookieReturnedIfCookieMatches(cookie);
}
@@ -301,8 +313,9 @@
mPreAuthInfo.shouldShowCredential(),
requireConfirmation,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
mState = STATE_AUTH_STARTED;
} catch (RemoteException e) {
@@ -369,7 +382,7 @@
final boolean shouldCancel = filter.apply(sensor);
Slog.d(TAG, "sensorId: " + sensor.id + ", shouldCancel: " + shouldCancel);
if (shouldCancel) {
- sensor.goToStateCancelling(mToken, mOpPackageName);
+ sensor.goToStateCancelling(mToken, mOpPackageName, mRequestId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
@@ -425,8 +438,9 @@
true /* credentialAllowed */,
false /* requireConfirmation */,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
} else {
mClientReceiver.onError(modality, error, vendorCode);
@@ -775,6 +789,8 @@
* @return true if this AuthSession is finished, e.g. should be set to null
*/
boolean onCancelAuthSession(boolean force) {
+ mCancelled = true;
+
final boolean authStarted = mState == STATE_AUTH_CALLED
|| mState == STATE_AUTH_STARTED
|| mState == STATE_AUTH_STARTED_UI_SHOWING;
@@ -820,6 +836,7 @@
return Utils.isCredentialRequested(mPromptInfo);
}
+ @VisibleForTesting
boolean allCookiesReceived() {
final int remainingCookies = mPreAuthInfo.numSensorsWaitingForCookie();
Slog.d(TAG, "Remaining cookies: " + remainingCookies);
@@ -839,6 +856,10 @@
return mState;
}
+ long getRequestId() {
+ return mRequestId;
+ }
+
private int statsModality() {
int modality = 0;
@@ -901,7 +922,9 @@
@Override
public String toString() {
return "State: " + mState
+ + ", cancelled: " + mCancelled
+ ", isCrypto: " + isCrypto()
- + ", PreAuthInfo: " + mPreAuthInfo;
+ + ", PreAuthInfo: " + mPreAuthInfo
+ + ", requestId: " + mRequestId;
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 8a842b5..0333c3e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -108,11 +108,11 @@
void goToStateWaitingForCookie(boolean requireConfirmation, IBinder token, long sessionId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication)
+ long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mCookie = cookie;
impl.prepareForAuthentication(requireConfirmation, token,
- sessionId, userId, sensorReceiver, opPackageName, mCookie,
+ sessionId, userId, sensorReceiver, opPackageName, requestId, mCookie,
allowBackgroundAuthentication);
mSensorState = STATE_WAITING_FOR_COOKIE;
}
@@ -129,8 +129,9 @@
mSensorState = STATE_AUTHENTICATING;
}
- void goToStateCancelling(IBinder token, String opPackageName) throws RemoteException {
- impl.cancelAuthenticationFromService(token, opPackageName);
+ void goToStateCancelling(IBinder token, String opPackageName, long requestId)
+ throws RemoteException {
+ impl.cancelAuthenticationFromService(token, opPackageName, requestId);
mSensorState = STATE_CANCELING;
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index b1d300c..e0775d4 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -83,6 +83,7 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
@@ -115,6 +116,7 @@
final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
private final Random mRandom = new Random();
+ @NonNull private final AtomicLong mRequestCounter;
@VisibleForTesting
IStatusBarService mStatusBarService;
@@ -194,6 +196,7 @@
SomeArgs args = (SomeArgs) msg.obj;
handleAuthenticate(
(IBinder) args.arg1 /* token */,
+ (long) args.arg6 /* requestId */,
(long) args.arg2 /* operationId */,
args.argi1 /* userid */,
(IBiometricServiceReceiver) args.arg3 /* receiver */,
@@ -204,7 +207,9 @@
}
case MSG_CANCEL_AUTHENTICATION: {
- handleCancelAuthentication();
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleCancelAuthentication((long) args.arg3 /* requestId */);
+ args.recycle();
break;
}
@@ -683,13 +688,13 @@
}
@Override // Binder call
- public void authenticate(IBinder token, long operationId, int userId,
+ public long authenticate(IBinder token, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
checkInternalPermission();
if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
Slog.e(TAG, "Unable to authenticate, one or more null arguments");
- return;
+ return -1;
}
if (!Utils.isValidAuthenticatorConfig(promptInfo)) {
@@ -706,6 +711,8 @@
}
}
+ final long requestId = mRequestCounter.incrementAndGet();
+
SomeArgs args = SomeArgs.obtain();
args.arg1 = token;
args.arg2 = operationId;
@@ -713,15 +720,23 @@
args.arg3 = receiver;
args.arg4 = opPackageName;
args.arg5 = promptInfo;
+ args.arg6 = requestId;
mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
+
+ return requestId;
}
@Override // Binder call
- public void cancelAuthentication(IBinder token, String opPackageName) {
+ public void cancelAuthentication(IBinder token, String opPackageName, long requestId) {
checkInternalPermission();
- mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = token;
+ args.arg2 = opPackageName;
+ args.arg3 = requestId;
+
+ mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
}
@Override // Binder call
@@ -1111,6 +1126,10 @@
return Settings.Secure.getInt(context.getContentResolver(),
CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
}
+
+ public AtomicLong getRequestGenerator() {
+ return new AtomicLong(0);
+ }
}
/**
@@ -1136,6 +1155,7 @@
mEnabledOnKeyguardCallbacks = new ArrayList<>();
mSettingObserver = mInjector.getSettingObserver(context, mHandler,
mEnabledOnKeyguardCallbacks);
+ mRequestCounter = mInjector.getRequestGenerator();
// TODO(b/193089985) This logic lives here (outside of CoexCoordinator) so that it doesn't
// need to depend on context. We can remove this code once the advanced logic is enabled
@@ -1349,7 +1369,7 @@
mCurrentAuthSession.onCookieReceived(cookie);
}
- private void handleAuthenticate(IBinder token, long operationId, int userId,
+ private void handleAuthenticate(IBinder token, long requestId, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
mHandler.post(() -> {
try {
@@ -1360,7 +1380,8 @@
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first
- + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo);
+ + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
+ + " requestId: " + requestId);
if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
@@ -1372,8 +1393,8 @@
promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
}
- authenticateInternal(token, operationId, userId, receiver, opPackageName,
- promptInfo, preAuthInfo);
+ authenticateInternal(token, requestId, operationId, userId, receiver,
+ opPackageName, promptInfo, preAuthInfo);
} else {
receiver.onError(preAuthStatus.first /* modality */,
preAuthStatus.second /* errorCode */,
@@ -1394,7 +1415,7 @@
* Note that this path is NOT invoked when the BiometricPrompt "Try again" button is pressed.
* In that case, see {@link #handleOnTryAgainPressed()}.
*/
- private void authenticateInternal(IBinder token, long operationId, int userId,
+ private void authenticateInternal(IBinder token, long requestId, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo,
PreAuthInfo preAuthInfo) {
Slog.d(TAG, "Creating authSession with authRequest: " + preAuthInfo);
@@ -1412,9 +1433,9 @@
final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
mCurrentAuthSession = new AuthSession(getContext(), mStatusBarService, mSysuiReceiver,
- mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, operationId, userId,
- mBiometricSensorReceiver, receiver, opPackageName, promptInfo, debugEnabled,
- mInjector.getFingerprintSensorProperties(getContext()));
+ mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, requestId,
+ operationId, userId, mBiometricSensorReceiver, receiver, opPackageName, promptInfo,
+ debugEnabled, mInjector.getFingerprintSensorProperties(getContext()));
try {
mCurrentAuthSession.goToInitialState();
} catch (RemoteException e) {
@@ -1422,11 +1443,21 @@
}
}
- private void handleCancelAuthentication() {
+ private void handleCancelAuthentication(long requestId) {
if (mCurrentAuthSession == null) {
Slog.e(TAG, "handleCancelAuthentication: AuthSession is null");
return;
}
+ if (mCurrentAuthSession.getRequestId() != requestId) {
+ // TODO: actually cancel the operation
+ // This can happen if the operation has been queued, but is cancelled before
+ // it reaches the head of the scheduler. Consider it a programming error for now
+ // and ignore it.
+ Slog.e(TAG, "handleCancelAuthentication: AuthSession mismatch current requestId: "
+ + mCurrentAuthSession.getRequestId() + " cancel for: " + requestId
+ + " (ignoring cancellation)");
+ return;
+ }
final boolean finished = mCurrentAuthSession.onCancelAuthSession(false /* force */);
if (finished) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index e5e1385..9764a16 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -26,6 +26,8 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.List;
import java.util.NoSuchElementException;
/**
@@ -70,26 +72,32 @@
}
/** Holder for wrapping multiple handlers into a single Callback. */
- protected static class CompositeCallback implements Callback {
+ public static class CompositeCallback implements Callback {
@NonNull
- private final Callback[] mCallbacks;
+ private final List<Callback> mCallbacks;
public CompositeCallback(@NonNull Callback... callbacks) {
- mCallbacks = callbacks;
+ mCallbacks = new ArrayList<>();
+
+ for (Callback callback : callbacks) {
+ if (callback != null) {
+ mCallbacks.add(callback);
+ }
+ }
}
@Override
public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- for (int i = 0; i < mCallbacks.length; i++) {
- mCallbacks[i].onClientStarted(clientMonitor);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onClientStarted(clientMonitor);
}
}
@Override
public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- for (int i = mCallbacks.length - 1; i >= 0; i--) {
- mCallbacks[i].onClientFinished(clientMonitor, success);
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).onClientFinished(clientMonitor, success);
}
}
}
@@ -101,6 +109,7 @@
private final int mSensorId; // sensorId as configured by the framework
@Nullable private IBinder mToken;
+ private long mRequestId;
@Nullable private ClientMonitorCallbackConverter mListener;
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
@@ -154,6 +163,7 @@
mSequentialId = sCount++;
mContext = context;
mToken = token;
+ mRequestId = -1;
mListener = listener;
mTargetUserId = userId;
mOwner = owner;
@@ -206,7 +216,6 @@
}
mToken = null;
}
- mListener = null;
}
@Override
@@ -255,10 +264,33 @@
return mToken;
}
- public final int getSensorId() {
+ public int getSensorId() {
return mSensorId;
}
+ /** Unique request id. */
+ public final long getRequestId() {
+ return mRequestId;
+ }
+
+ /** If a unique id has been set via {@link #setRequestId(long)} */
+ public final boolean hasRequestId() {
+ return mRequestId > 0;
+ }
+
+ /**
+ * A unique identifier used to tie this operation to a request (i.e an API invocation).
+ *
+ * Subclasses should not call this method if this operation does not have a direct
+ * correspondence to a request and {@link #hasRequestId()} will return false.
+ */
+ protected final void setRequestId(long id) {
+ if (id <= 0) {
+ throw new IllegalArgumentException("request id must be positive");
+ }
+ mRequestId = id;
+ }
+
@VisibleForTesting
public Callback getCallback() {
return mCallback;
@@ -271,6 +303,7 @@
+ ", proto=" + getProtoEnum()
+ ", owner=" + getOwnerString()
+ ", cookie=" + getCookie()
+ + ", requestId=" + getRequestId()
+ ", userId=" + getTargetUserId() + "}";
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index b20316e..361ec40 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -295,6 +295,7 @@
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mHandler.post(() -> {
+ clientMonitor.destroy();
if (mCurrentOperation == null) {
Slog.e(getTag(), "[Finishing] " + clientMonitor
+ " but current operation is null, success: " + success
@@ -642,22 +643,18 @@
/**
* Requests to cancel authentication or detection.
* @param token from the caller, should match the token passed in when requesting authentication
+ * @param requestId the id returned when requesting authentication
*/
- public void cancelAuthenticationOrDetection(IBinder token) {
- if (mCurrentOperation == null) {
- Slog.e(getTag(), "Unable to cancel authentication, null operation");
- return;
- }
- final boolean isCorrectClient = isAuthenticationOrDetectionOperation(mCurrentOperation);
- final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
+ public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
+ Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
+ + " current: " + mCurrentOperation
+ + " stack size: " + mPendingOperations.size());
- Slog.d(getTag(), "cancelAuthenticationOrDetection, isCorrectClient: " + isCorrectClient
- + ", tokenMatches: " + tokenMatches);
-
- if (isCorrectClient && tokenMatches) {
+ if (mCurrentOperation != null
+ && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
- } else if (!isCorrectClient) {
+ } else {
// Look through the current queue for all authentication clients for the specified
// token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
// all of them, instead of just the first one, since the API surface currently doesn't
@@ -665,8 +662,7 @@
// process. However, this generally does not happen anyway, and would be a class of
// bugs on its own.
for (Operation operation : mPendingOperations) {
- if (isAuthenticationOrDetectionOperation(operation)
- && operation.mClientMonitor.getToken() == token) {
+ if (canCancelAuthOperation(operation, token, requestId)) {
Slog.d(getTag(), "Marking " + operation
+ " as STATE_WAITING_IN_QUEUE_CANCELING");
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
@@ -675,10 +671,26 @@
}
}
- private boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
- final boolean isAuthentication = operation.mClientMonitor
- instanceof AuthenticationConsumer;
- final boolean isDetection = operation.mClientMonitor instanceof DetectionConsumer;
+ private static boolean canCancelAuthOperation(Operation operation, IBinder token,
+ long requestId) {
+ // TODO: restrict callers that can cancel without requestId (negative value)?
+ return isAuthenticationOrDetectionOperation(operation)
+ && operation.mClientMonitor.getToken() == token
+ && isMatchingRequestId(operation, requestId);
+ }
+
+ // By default, monitors are not associated with a request id to retain the original
+ // behavior (i.e. if no requestId is explicitly set then assume it matches)
+ private static boolean isMatchingRequestId(Operation operation, long requestId) {
+ return !operation.mClientMonitor.hasRequestId()
+ || operation.mClientMonitor.getRequestId() == requestId;
+ }
+
+ private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
+ final boolean isAuthentication =
+ operation.mClientMonitor instanceof AuthenticationConsumer;
+ final boolean isDetection =
+ operation.mClientMonitor instanceof DetectionConsumer;
return isAuthentication || isDetection;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index a15e14b..9191b8b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -31,7 +31,7 @@
/**
* A class to keep track of the enrollment state for a given client.
*/
-public abstract class EnrollClient<T> extends AcquisitionClient<T> {
+public abstract class EnrollClient<T> extends AcquisitionClient<T> implements EnrollmentModifier {
private static final String TAG = "Biometrics/EnrollClient";
@@ -40,6 +40,7 @@
protected final BiometricUtils mBiometricUtils;
private long mEnrollmentStartTimeMs;
+ private final boolean mHasEnrollmentsBeforeStarting;
/**
* @return true if the user has already enrolled the maximum number of templates.
@@ -56,6 +57,18 @@
mBiometricUtils = utils;
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
mTimeoutSec = timeoutSec;
+ mHasEnrollmentsBeforeStarting = hasEnrollments();
+ }
+
+ @Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = hasEnrollments();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
}
public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
new file mode 100644
index 0000000..c2f909b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+/**
+ * Interface for {@link BaseClientMonitor} subclasses that affect the state of enrollment.
+ */
+public interface EnrollmentModifier {
+
+ /**
+ * Callers should typically check this after
+ * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)}
+ *
+ * @return true if the user has gone from:
+ * 1) none-enrolled --> enrolled
+ * 2) enrolled --> none-enrolled
+ * but NOT any-enrolled --> more-enrolled
+ */
+ boolean hasEnrollmentStateChanged();
+
+ /**
+ * @return true if the user has any enrollments
+ */
+ boolean hasEnrollments();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 282261e..579dfd6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -40,7 +40,8 @@
* {@link #onRemoved(BiometricAuthenticator.Identifier, int)} returns true/
*/
public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Identifier, T>
- extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer {
+ extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer,
+ EnrollmentModifier {
private static final String TAG = "Biometrics/InternalCleanupClient";
@@ -61,6 +62,7 @@
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
private final List<S> mEnrolledList;
+ private final boolean mHasEnrollmentsBeforeStarting;
private BaseClientMonitor mCurrentTask;
private final Callback mEnumerateCallback = new Callback() {
@@ -115,6 +117,7 @@
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mEnrolledList = enrolledList;
+ mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
}
private void startCleanupUnknownHalTemplates() {
@@ -166,6 +169,18 @@
}
@Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = !mBiometricUtils
+ .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ }
+
+ @Override
public void onEnumerationResult(BiometricAuthenticator.Identifier identifier,
int remaining) {
if (!(mCurrentTask instanceof InternalEnumerateClient)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 383efce..2a6677e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -33,12 +33,13 @@
* A class to keep track of the remove state for a given client.
*/
public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, T>
- extends HalClientMonitor<T> implements RemovalConsumer {
+ extends HalClientMonitor<T> implements RemovalConsumer, EnrollmentModifier {
private static final String TAG = "Biometrics/RemovalClient";
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
+ private final boolean mHasEnrollmentsBeforeStarting;
public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@@ -49,6 +50,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
+ mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
}
@Override
@@ -91,6 +93,18 @@
}
@Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = !mBiometricUtils
+ .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ }
+
+ @Override
public int getProtoEnum() {
return BiometricsProto.CM_REMOVE;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 0bc4f1b..b2fd46d1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -61,10 +61,11 @@
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mFaceService.prepareForAuthentication(mSensorId, requireConfirmation, token, operationId,
- userId, sensorReceiver, opPackageName, cookie, allowBackgroundAuthentication);
+ userId, sensorReceiver, opPackageName, requestId, cookie,
+ allowBackgroundAuthentication);
}
@Override
@@ -73,9 +74,9 @@
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
- mFaceService.cancelAuthenticationFromService(mSensorId, token, opPackageName);
+ mFaceService.cancelAuthenticationFromService(mSensorId, token, opPackageName, requestId);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 12d6b08..675ee545 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -250,7 +250,7 @@
}
@Override // Binder call
- public void authenticate(final IBinder token, final long operationId, int userId,
+ public long authenticate(final IBinder token, final long operationId, int userId,
final IFaceServiceReceiver receiver, final String opPackageName,
boolean isKeyguardBypassEnabled) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
@@ -270,38 +270,38 @@
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
- return;
+ return -1;
}
- provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+ return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
0 /* cookie */,
new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
statsClient, isKeyguard, isKeyguardBypassEnabled);
}
@Override // Binder call
- public void detectFace(final IBinder token, final int userId,
+ public long detectFace(final IBinder token, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "detectFace called from non-sysui package: " + opPackageName);
- return;
+ return -1;
}
if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
// If this happens, something in KeyguardUpdateMonitor is wrong. This should only
// ever be invoked when the user is encrypted or lockdown.
Slog.e(TAG, "detectFace invoked when user is not encrypted or lockdown");
- return;
+ return -1;
}
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFace");
- return;
+ return -1;
}
- provider.second.scheduleFaceDetect(provider.first, token, userId,
+ return provider.second.scheduleFaceDetect(provider.first, token, userId,
new ClientMonitorCallbackConverter(receiver), opPackageName,
BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@@ -309,8 +309,8 @@
@Override // Binder call
public void prepareForAuthentication(int sensorId, boolean requireConfirmation,
IBinder token, long operationId, int userId,
- IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie,
- boolean allowBackgroundAuthentication) {
+ IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+ int cookie, boolean allowBackgroundAuthentication) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -322,9 +322,9 @@
final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients
final boolean restricted = true; // BiometricPrompt is always restricted
provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
- new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
- BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
- isKeyguardBypassEnabled);
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
}
@Override // Binder call
@@ -341,7 +341,8 @@
}
@Override // Binder call
- public void cancelAuthentication(final IBinder token, final String opPackageName) {
+ public void cancelAuthentication(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -350,11 +351,12 @@
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
- public void cancelFaceDetect(final IBinder token, final String opPackageName) {
+ public void cancelFaceDetect(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "cancelFaceDetect called from non-sysui package: "
@@ -368,12 +370,12 @@
return;
}
- provider.second.cancelFaceDetect(provider.first, token);
+ provider.second.cancelFaceDetect(provider.first, token, requestId);
}
@Override // Binder call
public void cancelAuthenticationFromService(int sensorId, final IBinder token,
- final String opPackageName) {
+ final String opPackageName, final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -382,7 +384,7 @@
return;
}
- provider.cancelAuthentication(sensorId, token);
+ provider.cancelAuthentication(sensorId, token, requestId);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 93ab1b6..e099ba3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -101,18 +101,23 @@
void cancelEnrollment(int sensorId, @NonNull IBinder token);
- void scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
+ long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
int statsClient);
- void cancelFaceDetect(int sensorId, @NonNull IBinder token);
+ void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId);
- void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
- void cancelAuthentication(int sensorId, @NonNull IBinder token);
+ void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
+
+ void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId);
void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index d66a279..cbceba6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -65,7 +65,8 @@
@FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
FaceAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
@@ -76,6 +77,7 @@
BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
+ setRequestId(requestId);
mUsageStats = usageStats;
mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 1e73ac5..2ef0911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -43,11 +43,13 @@
@Nullable private ICancellationSignal mCancellationSignal;
public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 718b9da..4bae775 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -65,6 +65,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Provider for a single instance of the {@link IFace} HAL.
@@ -83,6 +84,8 @@
@NonNull private final UsageStats mUsageStats;
@NonNull private final ActivityTaskManager mActivityTaskManager;
@NonNull private final BiometricTaskStackListener mTaskStackListener;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@Nullable private IFace mDaemon;
@@ -110,8 +113,8 @@
&& !client.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mSensors.valueAt(i).getScheduler()
- .cancelAuthenticationOrDetection(client.getToken());
+ mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
}
@@ -356,34 +359,39 @@
}
@Override
- public void scheduleFaceDetect(int sensorId, @NonNull IBinder token,
+ public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
int userId, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
+
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceDetectClient client = new FaceDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId, opPackageName,
+ mSensors.get(sensorId).getLazySession(),
+ token, id, callback, userId, opPackageName,
sensorId, isStrongBiometric, statsClient);
scheduleForSensor(sensorId, client);
});
+
+ return id;
}
@Override
- public void cancelFaceDetect(int sensorId, @NonNull IBinder token) {
+ public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, boolean restricted, int statsClient,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
+ mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
+ userId, operationId, restricted, opPackageName, cookie,
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, isKeyguardBypassEnabled);
@@ -392,9 +400,23 @@
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
+ opPackageName, id, restricted, statsClient,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
+
+ return id;
+ }
+
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index d05333d..f4dcbbb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -87,6 +87,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
@@ -115,6 +116,8 @@
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
private final int mSensorId;
private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
@@ -605,7 +608,7 @@
}
@Override
- public void scheduleFaceDetect(int sensorId, @NonNull IBinder token,
+ public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
int userId, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, int statsClient) {
throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
@@ -613,7 +616,7 @@
}
@Override
- public void cancelFaceDetect(int sensorId, @NonNull IBinder token) {
+ public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
+ "forget to check the supportsFaceDetection flag?");
}
@@ -621,26 +624,38 @@
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull String opPackageName, boolean restricted, int statsClient,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
- mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName,
- cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric,
- statsClient, mLockoutTracker, mUsageStats, allowBackgroundAuthentication,
- isKeyguardBypassEnabled);
+ mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
+ opPackageName, cookie, false /* requireConfirmation */, mSensorId,
+ isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
mScheduler.scheduleClientMonitor(client);
});
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
- mHandler.post(() -> {
- mScheduler.cancelAuthenticationOrDetection(token);
- });
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, receiver,
+ opPackageName, id, restricted, statsClient,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
+
+ return id;
+ }
+
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 33950af..40f2801 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -57,7 +57,8 @@
private int mLastAcquire;
FaceAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
@@ -68,6 +69,7 @@
BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
+ setRequestId(requestId);
mUsageStats = usageStats;
final Resources resources = getContext().getResources();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 1e59429..52d887a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -61,10 +61,10 @@
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mFingerprintService.prepareForAuthentication(mSensorId, token, operationId, userId,
- sensorReceiver, opPackageName, cookie, allowBackgroundAuthentication);
+ sensorReceiver, opPackageName, requestId, cookie, allowBackgroundAuthentication);
}
@Override
@@ -73,9 +73,10 @@
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
- mFingerprintService.cancelAuthenticationFromService(mSensorId, token, opPackageName);
+ mFingerprintService.cancelAuthenticationFromService(
+ mSensorId, token, opPackageName, requestId);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 183fabd..f35bb7f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -35,6 +35,7 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
@@ -62,11 +63,13 @@
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Pair;
@@ -111,6 +114,7 @@
private final FingerprintServiceWrapper mServiceWrapper;
@NonNull private final List<ServiceProvider> mServiceProviders;
@NonNull private final FingerprintStateCallback mFingerprintStateCallback;
+ @NonNull private final Handler mHandler;
@GuardedBy("mLock")
@NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
@@ -125,6 +129,37 @@
*/
public void registerFingerprintStateListener(@NonNull IFingerprintStateListener listener) {
mFingerprintStateCallback.registerFingerprintStateListener(listener);
+ broadcastCurrentEnrollmentState(listener);
+ }
+
+ /**
+ * @param listener if non-null, notifies only this listener. if null, notifies all listeners
+ * in {@link FingerprintStateCallback}. This is slightly ugly, but reduces
+ * redundant code.
+ */
+ private void broadcastCurrentEnrollmentState(@Nullable IFingerprintStateListener listener) {
+ final UserManager um = UserManager.get(getContext());
+ synchronized (mLock) {
+ // Update the new listener with current state of all sensors
+ for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
+ final ServiceProvider provider = getProviderForSensor(prop.sensorId);
+ for (UserInfo userInfo : um.getAliveUsers()) {
+ final boolean enrolled = !provider
+ .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
+
+ // Defer this work and allow the loop to release the lock sooner
+ mHandler.post(() -> {
+ if (listener != null) {
+ mFingerprintStateCallback.notifyFingerprintEnrollmentStateChanged(
+ listener, userInfo.id, prop.sensorId, enrolled);
+ } else {
+ mFingerprintStateCallback.notifyAllFingerprintEnrollmentStateChanged(
+ userInfo.id, prop.sensorId, enrolled);
+ }
+ });
+ }
+ }
+ }
}
/**
@@ -143,8 +178,7 @@
return null;
}
- return provider.createTestSession(sensorId, callback, mFingerprintStateCallback,
- opPackageName);
+ return provider.createTestSession(sensorId, callback, opPackageName);
}
@Override
@@ -227,7 +261,7 @@
}
provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
- receiver, opPackageName, enrollReason, mFingerprintStateCallback);
+ receiver, opPackageName, enrollReason);
}
@Override // Binder call
@@ -245,7 +279,7 @@
@SuppressWarnings("deprecation")
@Override // Binder call
- public void authenticate(final IBinder token, final long operationId,
+ public long authenticate(final IBinder token, final long operationId,
final int sensorId, final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName) {
final int callingUid = Binder.getCallingUid();
@@ -255,7 +289,7 @@
if (!canUseFingerprint(opPackageName, true /* requireForeground */, callingUid,
callingPid, callingUserId)) {
Slog.w(TAG, "Authenticate rejecting package: " + opPackageName);
- return;
+ return -1;
}
// Keyguard check must be done on the caller's binder identity, since it also checks
@@ -270,7 +304,7 @@
// SafetyNet for b/79776455
EventLog.writeEvent(0x534e4554, "79776455");
Slog.e(TAG, "Authenticate invoked when user is encrypted or lockdown");
- return;
+ return -1;
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -290,7 +324,7 @@
}
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
- return;
+ return -1;
}
final FingerprintSensorPropertiesInternal sensorProps =
@@ -299,18 +333,17 @@
&& sensorProps != null && sensorProps.isAnyUdfpsType()) {
identity = Binder.clearCallingIdentity();
try {
- authenticateWithPrompt(operationId, sensorProps, userId, receiver);
+ return authenticateWithPrompt(operationId, sensorProps, userId, receiver);
} finally {
Binder.restoreCallingIdentity(identity);
}
- } else {
- provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
- 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
- restricted, statsClient, isKeyguard, mFingerprintStateCallback);
}
+ return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
+ restricted, statsClient, isKeyguard);
}
- private void authenticateWithPrompt(
+ private long authenticateWithPrompt(
final long operationId,
@NonNull final FingerprintSensorPropertiesInternal props,
final int userId,
@@ -387,41 +420,41 @@
}
};
- biometricPrompt.authenticateUserForOperation(
+ return biometricPrompt.authenticateUserForOperation(
new CancellationSignal(), executor, promptCallback, userId, operationId);
}
@Override
- public void detectFingerprint(final IBinder token, final int userId,
+ public long detectFingerprint(final IBinder token, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
- return;
+ return -1;
}
if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
// If this happens, something in KeyguardUpdateMonitor is wrong. This should only
// ever be invoked when the user is encrypted or lockdown.
Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
- return;
+ return -1;
}
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
- return;
+ return -1;
}
- provider.second.scheduleFingerDetect(provider.first, token, userId,
+ return provider.second.scheduleFingerDetect(provider.first, token, userId,
new ClientMonitorCallbackConverter(receiver), opPackageName,
- BiometricsProtoEnums.CLIENT_KEYGUARD, mFingerprintStateCallback);
+ BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@Override // Binder call
public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication) {
+ long requestId, int cookie, boolean allowBackgroundAuthentication) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -432,9 +465,9 @@
final boolean restricted = true; // BiometricPrompt is always restricted
provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
- new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
- BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
- mFingerprintStateCallback);
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+ allowBackgroundAuthentication);
}
@Override // Binder call
@@ -452,7 +485,8 @@
@Override // Binder call
- public void cancelAuthentication(final IBinder token, final String opPackageName) {
+ public void cancelAuthentication(final IBinder token, final String opPackageName,
+ long requestId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
@@ -469,11 +503,12 @@
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
- public void cancelFingerprintDetect(final IBinder token, final String opPackageName) {
+ public void cancelFingerprintDetect(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
@@ -489,12 +524,12 @@
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
- final String opPackageName) {
+ final String opPackageName, final long requestId) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
@@ -506,7 +541,7 @@
return;
}
- provider.cancelAuthentication(sensorId, token);
+ provider.cancelAuthentication(sensorId, token, requestId);
}
@Override // Binder call
@@ -686,27 +721,6 @@
.isEmpty();
}
- @Override // Binder call
- public boolean hasEnrolledTemplatesForAnySensor(int userId,
- @NonNull List<FingerprintSensorPropertiesInternal> sensors,
- @NonNull String opPackageName) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-
- for (FingerprintSensorPropertiesInternal prop : sensors) {
- final ServiceProvider provider = getProviderForSensor(prop.sensorId);
- if (provider == null) {
- Slog.w(TAG, "Null provider for sensorId: " + prop.sensorId
- + ", caller: " + opPackageName);
- continue;
- }
-
- if (!provider.getEnrolledFingerprints(prop.sensorId, userId).isEmpty()) {
- return true;
- }
- }
- return false;
- }
-
public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
@@ -796,10 +810,12 @@
&& Settings.Secure.getIntForUser(getContext().getContentResolver(),
Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
UserHandle.USER_CURRENT) != 0) {
- fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), hidlSensor,
+ fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
+ mFingerprintStateCallback, hidlSensor,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else {
- fingerprint21 = Fingerprint21.newInstance(getContext(), hidlSensor,
+ fingerprint21 = Fingerprint21.newInstance(getContext(),
+ mFingerprintStateCallback, hidlSensor,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
mServiceProviders.add(fingerprint21);
@@ -822,8 +838,9 @@
try {
final SensorProps[] props = fp.getSensorProps();
final FingerprintProvider provider =
- new FingerprintProvider(getContext(), props, instance,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
+ instance, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher);
mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -877,6 +894,7 @@
}
}
+ broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
broadcastAllAuthenticatorsRegistered();
});
}
@@ -974,6 +992,7 @@
mFingerprintStateCallback = new FingerprintStateCallback();
mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
mSensorProps = new ArrayList<>();
+ mHandler = new Handler(Looper.getMainLooper());
}
// Notifies the callbacks that all of the authenticators have been registered and removes the
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
index 5f998d8..0050a89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
@@ -23,6 +23,7 @@
import static android.hardware.fingerprint.FingerprintStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintStateListener;
import android.os.RemoteException;
@@ -31,6 +32,9 @@
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.EnrollmentModifier;
+import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.fingerprint.hidl.FingerprintEnrollClient;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -39,9 +43,11 @@
* A callback for receiving notifications about changes in fingerprint state.
*/
public class FingerprintStateCallback implements BaseClientMonitor.Callback {
- private @FingerprintStateListener.State int mFingerprintState;
+
@NonNull private final CopyOnWriteArrayList<IFingerprintStateListener>
- mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+ mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+
+ private @FingerprintStateListener.State int mFingerprintState;
public FingerprintStateCallback() {
mFingerprintState = STATE_IDLE;
@@ -54,8 +60,9 @@
@Override
public void onClientStarted(@NonNull BaseClientMonitor client) {
final int previousFingerprintState = mFingerprintState;
+
if (client instanceof AuthenticationClient) {
- AuthenticationClient authClient = (AuthenticationClient) client;
+ final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client;
if (authClient.isKeyguard()) {
mFingerprintState = STATE_KEYGUARD_AUTH;
} else if (authClient.isBiometricPrompt()) {
@@ -70,6 +77,7 @@
"Other authentication client: " + Utils.getClientName(client));
mFingerprintState = STATE_IDLE;
}
+
Slog.d(FingerprintService.TAG, "Fps state updated from " + previousFingerprintState
+ " to " + mFingerprintState + ", client " + client);
notifyFingerprintStateListeners(mFingerprintState);
@@ -81,6 +89,18 @@
Slog.d(FingerprintService.TAG,
"Client finished, fps state updated to " + mFingerprintState + ", client "
+ client);
+
+ if (client instanceof EnrollmentModifier) {
+ EnrollmentModifier enrollmentModifier = (EnrollmentModifier) client;
+ final boolean enrollmentStateChanged = enrollmentModifier.hasEnrollmentStateChanged();
+ Slog.d(FingerprintService.TAG, "Enrollment state changed: " + enrollmentStateChanged);
+ if (enrollmentStateChanged) {
+ notifyAllFingerprintEnrollmentStateChanged(client.getTargetUserId(),
+ client.getSensorId(),
+ enrollmentModifier.hasEnrollments());
+ }
+ }
+
notifyFingerprintStateListeners(mFingerprintState);
}
@@ -95,6 +115,32 @@
}
/**
+ * This should be invoked when:
+ * 1) Enrolled --> None-enrolled
+ * 2) None-enrolled --> enrolled
+ * 3) HAL becomes ready
+ * 4) Listener is registered
+ */
+ void notifyAllFingerprintEnrollmentStateChanged(int userId, int sensorId,
+ boolean hasEnrollments) {
+ for (IFingerprintStateListener listener : mFingerprintStateListeners) {
+ notifyFingerprintEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
+ }
+ }
+
+ /**
+ * Notifies the listener of enrollment state changes.
+ */
+ void notifyFingerprintEnrollmentStateChanged(@NonNull IFingerprintStateListener listener,
+ int userId, int sensorId, boolean hasEnrollments) {
+ try {
+ listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
+ } catch (RemoteException e) {
+ Slog.e(FingerprintService.TAG, "Remote exception", e);
+ }
+ }
+
+ /**
* Enables clients to register a FingerprintStateListener. Used by FingerprintService to forward
* updates in fingerprint sensor state to the SideFpNsEventHandler
* @param listener
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 706ac10..1772f81 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -90,25 +90,27 @@
*/
void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFingerprintServiceReceiver receiver,
- @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
void cancelEnrollment(int sensorId, @NonNull IBinder token);
- void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ int statsClient);
void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication);
+
+ long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ boolean allowBackgroundAuthentication);
void startPreparedClient(int sensorId, int cookie);
- void cancelAuthentication(int sensorId, @NonNull IBinder token);
+ void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId);
void scheduleRemove(int sensorId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
@@ -163,6 +165,5 @@
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 29f2f20..2b50b96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@
Utils.checkPermission(mContext, TEST_BIOMETRIC);
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- mFingerprintStateCallback);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 37ee76a..b405be7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -61,7 +61,8 @@
private boolean mIsPointerDown;
FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
int sensorId, boolean isStrongBiometric, int statsClient,
@@ -74,6 +75,7 @@
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
+ setRequestId(requestId);
mLockoutCache = lockoutCache;
mUdfpsOverlayController = udfpsOverlayController;
mSensorProps = sensorProps;
@@ -164,13 +166,17 @@
@Override
protected void stopHalOperation() {
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- try {
- mCancellationSignal.cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
+ if (mCancellationSignal != null) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ } else {
+ Slog.e(TAG, "cancellation signal was null");
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index c5dc449..da91cdd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -47,13 +47,15 @@
@Nullable private ICancellationSignal mCancellationSignal;
FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
@Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric,
int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
mUdfpsOverlayController = udfpsOverlayController;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 102b074..ca83dda 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -71,6 +71,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Provider for a single instance of the {@link IFingerprint} HAL.
@@ -81,6 +82,7 @@
private boolean mTestHalEnabled;
@NonNull private final Context mContext;
+ @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -88,6 +90,8 @@
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull private final ActivityTaskManager mActivityTaskManager;
@NonNull private final BiometricTaskStackListener mTaskStackListener;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@@ -118,8 +122,8 @@
&& !client.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mSensors.valueAt(i).getScheduler()
- .cancelAuthenticationOrDetection(client.getToken());
+ mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
}
@@ -127,10 +131,13 @@
}
}
- public FingerprintProvider(@NonNull Context context, @NonNull SensorProps[] props,
- @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ public FingerprintProvider(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
+ @NonNull SensorProps[] props, @NonNull String halInstanceName,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
mContext = context;
+ mFingerprintStateCallback = fingerprintStateCallback;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
mHandler = new Handler(Looper.getMainLooper());
@@ -332,8 +339,7 @@
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
.maxEnrollmentsPerUser;
@@ -347,13 +353,13 @@
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- fingerprintStateCallback.onClientStarted(clientMonitor);
+ mFingerprintStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- fingerprintStateCallback.onClientFinished(clientMonitor, success);
+ mFingerprintStateCallback.onClientFinished(clientMonitor, success);
if (success) {
scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
scheduleInvalidationRequest(sensorId, userId);
@@ -369,48 +375,62 @@
}
@Override
- public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- scheduleForSensor(sensorId, client, fingerprintStateCallback);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
+
+ return id;
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
+ mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
+ userId, operationId, restricted, opPackageName, cookie,
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
- scheduleForSensor(sensorId, client, fingerprintStateCallback);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
}
@Override
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
+ opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+
+ return id;
+ }
+
+ @Override
public void startPreparedClient(int sensorId, int cookie) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler().startPreparedClient(cookie));
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
@@ -444,7 +464,7 @@
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
}
@@ -459,7 +479,8 @@
mContext.getOpPackageName(), sensorId, enrolledList,
FingerprintUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client, callback);
+ scheduleForSensor(sensorId, client, new BaseClientMonitor.CompositeCallback(callback,
+ mFingerprintStateCallback));
});
}
@@ -604,9 +625,8 @@
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName) {
- return mSensors.get(sensorId).createTestSession(callback, fingerprintStateCallback);
+ return mSensors.get(sensorId).createTestSession(callback, mFingerprintStateCallback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index c00daff..79c6b1b3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@
Utils.checkPermission(mContext, TEST_BIOMETRIC);
mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- mFingerprintStateCallback);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 2f5b5c7..d2882aa4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -88,6 +88,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
@@ -101,6 +102,7 @@
private boolean mTestHalEnabled;
final Context mContext;
+ @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
private final ActivityTaskManager mActivityTaskManager;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
private final BiometricScheduler mScheduler;
@@ -115,6 +117,8 @@
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
private final boolean mIsUdfps;
private final int mSensorId;
@@ -142,7 +146,8 @@
&& !client.isAlreadyDone()) {
Slog.e(TAG, "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mScheduler.cancelAuthenticationOrDetection(client.getToken());
+ mScheduler.cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
});
@@ -313,11 +318,13 @@
}
Fingerprint21(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
mContext = context;
+ mFingerprintStateCallback = fingerprintStateCallback;
mSensorProperties = sensorProps;
mSensorId = sensorProps.sensorId;
@@ -347,6 +354,7 @@
}
public static Fingerprint21 newInstance(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -358,8 +366,8 @@
final HalResultController controller = new HalResultController(sensorProps.sensorId,
context, handler,
scheduler);
- return new Fingerprint21(context, sensorProps, scheduler, handler, lockoutResetDispatcher,
- controller);
+ return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
}
@Override
@@ -553,8 +561,7 @@
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -566,13 +573,13 @@
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- fingerprintStateCallback.onClientStarted(clientMonitor);
+ mFingerprintStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- fingerprintStateCallback.onClientFinished(clientMonitor, success);
+ mFingerprintStateCallback.onClientFinished(clientMonitor, success);
if (success) {
// Update authenticatorIds
scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
@@ -591,51 +598,65 @@
}
@Override
- public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mLazyDaemon, token, listener, userId, opPackageName,
+ mLazyDaemon, token, id, listener, userId, opPackageName,
mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
+
+ return id;
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mLazyDaemon, token, listener, userId, operationId, restricted,
- opPackageName, cookie, false /* requireConfirmation */,
+ mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
+ restricted, opPackageName, cookie, false /* requireConfirmation */,
mSensorProperties.sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
allowBackgroundAuthentication, mSensorProperties);
- mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@Override
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener,
+ opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+
+ return id;
+ }
+
+ @Override
public void startPreparedClient(int sensorId, int cookie) {
mHandler.post(() -> mScheduler.startPreparedClient(cookie));
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
Slog.d(TAG, "cancelAuthentication, sensorId: " + sensorId);
- mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token));
+ mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
}
@Override
@@ -649,7 +670,7 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -666,7 +687,7 @@
0 /* fingerprintId */, userId, opPackageName,
FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -688,7 +709,8 @@
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable BaseClientMonitor.Callback callback) {
- scheduleInternalCleanup(userId, callback);
+ scheduleInternalCleanup(userId, new BaseClientMonitor.CompositeCallback(callback,
+ mFingerprintStateCallback));
}
@Override
@@ -896,9 +918,8 @@
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName) {
return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
- fingerprintStateCallback, this, mHalResultController);
+ mFingerprintStateCallback, this, mHalResultController);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 24ce867..79ad8e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -26,6 +26,7 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -42,6 +43,7 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.util.ArrayList;
@@ -270,6 +272,7 @@
}
public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -280,8 +283,8 @@
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
- return new Fingerprint21UdfpsMock(context, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
+ handler, lockoutResetDispatcher, controller);
}
private static abstract class FakeFingerRunnable implements Runnable {
@@ -400,17 +403,19 @@
// internal preemption logic is not run.
mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
operationId, user, cookie, listener, opPackageName, restricted, statsClient,
- isKeyguard, null /* fingerprintStateCallback */);
+ isKeyguard);
}
}
private Fingerprint21UdfpsMock(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull TestableBiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull MockHalResultController controller) {
- super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+ super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 5060744..7d95ec0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -59,7 +59,8 @@
private boolean mIsPointerDown;
FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
int sensorId, boolean isStrongBiometric, int statsClient,
@@ -73,6 +74,7 @@
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
+ setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
mUdfpsOverlayController = udfpsOverlayController;
mSensorProps = sensorProps;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 8e73ee6b..147a206 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -52,13 +52,15 @@
private boolean mIsPointerDown;
public FingerprintDetectClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
boolean isStrongBiometric, int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mUdfpsOverlayController = udfpsOverlayController;
mIsStrongBiometric = isStrongBiometric;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 4918185..5c0c362 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -59,7 +59,7 @@
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long sessionId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
}
@@ -68,7 +68,7 @@
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
}
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index a56a8ea..72e900b 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -25,9 +25,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
-import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
-import static android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -77,6 +75,8 @@
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -224,12 +224,13 @@
"Can't get TelephonyManager for subId %d", subId));
}
- subscriberId = tele.getSubscriberId();
- mNetworkTemplate = new NetworkTemplate(
- NetworkTemplate.MATCH_MOBILE, subscriberId, new String[] { subscriberId },
- null, NetworkStats.METERED_YES, NetworkStats.ROAMING_ALL,
- NetworkStats.DEFAULT_NETWORK_NO, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
- SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ subscriberId = Objects.requireNonNull(tele.getSubscriberId(),
+ "Null subscriber Id for subId " + subId);
+ mNetworkTemplate = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
+ .setSubscriberIds(Set.of(subscriberId))
+ .setMeteredness(NetworkStats.METERED_YES)
+ .setDefaultNetworkStatus(NetworkStats.DEFAULT_NETWORK_NO)
+ .build();
mUsageCallback = new UsageCallback() {
@Override
public void onThresholdReached(int networkType, String subscriberId) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index bf4ef48..2c666b5 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1446,7 +1446,10 @@
// parameters. If that fails, disconnect.
if (oldConfig != null
&& updateLinkPropertiesInPlaceIfPossible(mNetworkAgent, oldConfig)) {
- // Keep mNetworkAgent unchanged
+ // Update underlying networks if it is changed.
+ if (!Arrays.equals(oldConfig.underlyingNetworks, config.underlyingNetworks)) {
+ setUnderlyingNetworks(config.underlyingNetworks);
+ }
} else {
// Initialize the state for a new agent, while keeping the old one connected
// in case this new connection fails.
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4c9d0f2..2ae5cbb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -590,7 +590,7 @@
newIndex = i - newStart;
final float newBacklightVal;
final float newNitsVal;
- isLastValue = mRawBacklight[i] > mBacklightMaximum
+ isLastValue = mRawBacklight[i] >= mBacklightMaximum
|| i >= mRawBacklight.length - 1;
// Clamp beginning and end to valid backlight values.
if (newIndex == 0) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index afd1889..c220043 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -192,7 +192,9 @@
private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
- private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
+ // This value needs to be in sync with the threshold
+ // in RefreshRateConfigs::getFrameRateDivider.
+ private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.0009f;
private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
@@ -812,7 +814,7 @@
// Override the refresh rate only if it is a divider of the current
// refresh rate. This calculation needs to be in sync with the native code
- // in RefreshRateConfigs::getRefreshRateDividerForUid
+ // in RefreshRateConfigs::getFrameRateDivider
Display.Mode currentMode = info.getMode();
float numPeriods = currentMode.getRefreshRate() / frameRateHz;
float numPeriodsRound = Math.round(numPeriods);
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 329a696..bc2a234 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
+import static android.os.PowerManager.BRIGHTNESS_INVALID;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,9 +37,15 @@
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.net.Uri;
import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Temperature;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -48,12 +55,14 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayInfo;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.display.utils.AmbientFilter;
@@ -72,7 +81,6 @@
import java.util.Locale;
import java.util.Objects;
-
/**
* The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
* picked by the system based on system-wide and display-specific configuration.
@@ -87,6 +95,8 @@
private static final int MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED = 4;
private static final int MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED = 5;
private static final int MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED = 6;
+ private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7;
+ private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8;
// Special ID used to indicate that given vote is to be applied globally, rather than to a
// specific display.
@@ -108,6 +118,7 @@
private final UdfpsObserver mUdfpsObserver;
private final SensorObserver mSensorObserver;
private final HbmObserver mHbmObserver;
+ private final SkinThermalStatusObserver mSkinThermalStatusObserver;
private final DeviceConfigInterface mDeviceConfig;
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
@@ -147,7 +158,7 @@
mAppRequestObserver = new AppRequestObserver();
mSettingsObserver = new SettingsObserver(context, handler);
mDisplayObserver = new DisplayObserver(context, handler);
- mBrightnessObserver = new BrightnessObserver(context, handler);
+ mBrightnessObserver = new BrightnessObserver(context, handler, injector);
mUdfpsObserver = new UdfpsObserver();
final BallotBox ballotBox = (displayId, priority, vote) -> {
synchronized (mLock) {
@@ -155,8 +166,10 @@
}
};
mSensorObserver = new SensorObserver(context, ballotBox, injector);
- mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler());
+ mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+ mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
+ mDeviceConfigDisplaySettings);
mDeviceConfig = injector.getDeviceConfig();
mAlwaysRespectAppRequest = false;
}
@@ -174,6 +187,7 @@
mBrightnessObserver.observe(sensorManager);
mSensorObserver.observe();
mHbmObserver.observe();
+ mSkinThermalStatusObserver.observe();
synchronized (mLock) {
// We may have a listener already registered before the call to start, so go ahead and
// notify them to pick up our newly initialized state.
@@ -609,6 +623,7 @@
mUdfpsObserver.dumpLocked(pw);
mSensorObserver.dumpLocked(pw);
mHbmObserver.dumpLocked(pw);
+ mSkinThermalStatusObserver.dumpLocked(pw);
}
}
@@ -717,6 +732,10 @@
return mUdfpsObserver;
}
+ @VisibleForTesting
+ HbmObserver getHbmObserver() {
+ return mHbmObserver;
+ }
@VisibleForTesting
DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
@@ -787,6 +806,19 @@
(DesiredDisplayModeSpecsListener) msg.obj;
desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged();
break;
+
+ case MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED: {
+ int refreshRateInHbmSunlight = msg.arg1;
+ mHbmObserver.onDeviceConfigRefreshRateInHbmSunlightChanged(
+ refreshRateInHbmSunlight);
+ break;
+ }
+
+ case MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED: {
+ int refreshRateInHbmHdr = msg.arg1;
+ mHbmObserver.onDeviceConfigRefreshRateInHbmHdrChanged(refreshRateInHbmHdr);
+ break;
+ }
}
}
}
@@ -913,16 +945,19 @@
// result is a range.
public static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
+ // High-brightness-mode may need a specific range of refresh-rates to function properly.
+ public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
+
// SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
// It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
- public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2;
+ public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 3;
// APP_REQUEST_REFRESH_RATE_RANGE is used to for internal apps to limit the refresh
// rate in certain cases, mostly to preserve power.
// @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
// @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
// It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
- public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 3;
+ public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 4;
// We split the app request into different priorities in case we can satisfy one desire
// without the other.
@@ -937,32 +972,32 @@
// The preferred refresh rate is set on the main surface of the app outside of
// DisplayModeDirector.
// @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
- public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 4;
- public static final int PRIORITY_APP_REQUEST_SIZE = 5;
+ public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
+ public static final int PRIORITY_APP_REQUEST_SIZE = 6;
// SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
// of low priority voters. It votes [0, max(PEAK, MIN)]
- public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 6;
+ public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 7;
// LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
- public static final int PRIORITY_LOW_POWER_MODE = 7;
+ public static final int PRIORITY_LOW_POWER_MODE = 8;
// PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
// higher priority voters' result is a range, it will fix the rate to a single choice.
// It's used to avoid refresh rate switches in certain conditions which may result in the
// user seeing the display flickering when the switches occur.
- public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 8;
+ public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 9;
- // High-brightness-mode may need a specific range of refresh-rates to function properly.
- public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 9;
+ // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
+ public static final int PRIORITY_SKIN_TEMPERATURE = 10;
// The proximity sensor needs the refresh rate to be locked in order to function, so this is
// set to a high priority.
- public static final int PRIORITY_PROXIMITY = 10;
+ public static final int PRIORITY_PROXIMITY = 11;
// The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
// to function, so this needs to be the highest priority of all votes.
- public static final int PRIORITY_UDFPS = 11;
+ public static final int PRIORITY_UDFPS = 12;
// Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
// APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -1057,6 +1092,8 @@
return "PRIORITY_PROXIMITY";
case PRIORITY_LOW_POWER_MODE:
return "PRIORITY_LOW_POWER_MODE";
+ case PRIORITY_SKIN_TEMPERATURE:
+ return "PRIORITY_SKIN_TEMPERATURE";
case PRIORITY_UDFPS:
return "PRIORITY_UDFPS";
case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
@@ -1396,8 +1433,6 @@
@Override
public void onDisplayChanged(int displayId) {
updateDisplayModes(displayId);
- // TODO: Break the coupling between DisplayObserver and BrightnessObserver.
- mBrightnessObserver.onDisplayChanged(displayId);
}
private void updateDisplayModes(int displayId) {
@@ -1434,7 +1469,7 @@
* {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
*/
@VisibleForTesting
- public class BrightnessObserver extends ContentObserver {
+ public class BrightnessObserver implements DisplayManager.DisplayListener {
private final static int LIGHT_SENSOR_RATE_MS = 250;
private int[] mLowDisplayBrightnessThresholds;
private int[] mLowAmbientBrightnessThresholds;
@@ -1457,6 +1492,8 @@
private int mBrightness = -1;
private final Context mContext;
+ private final Injector mInjector;
+ private final Handler mHandler;
// Enable light sensor only when mShouldObserveAmbientLowChange is true or
// mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
@@ -1469,9 +1506,11 @@
private int mRefreshRateInLowZone;
private int mRefreshRateInHighZone;
- BrightnessObserver(Context context, Handler handler) {
- super(handler);
+ BrightnessObserver(Context context, Handler handler, Injector injector) {
mContext = context;
+ mHandler = handler;
+ mInjector = injector;
+
mLowDisplayBrightnessThresholds = context.getResources().getIntArray(
R.array.config_brightnessThresholdsOfPeakRefreshRate);
mLowAmbientBrightnessThresholds = context.getResources().getIntArray(
@@ -1538,8 +1577,7 @@
public void observe(SensorManager sensorManager) {
mSensorManager = sensorManager;
final ContentResolver cr = mContext.getContentResolver();
- mBrightness = Settings.System.getIntForUser(cr,
- Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
+ mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
// DeviceConfig is accessible after system ready.
int[] lowDisplayBrightnessThresholds =
@@ -1572,6 +1610,10 @@
restartObserver();
mDeviceConfigDisplaySettings.startListening();
+
+ mInjector.registerDisplayListener(this, mHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED |
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
}
public void setLoggingEnabled(boolean loggingEnabled) {
@@ -1687,28 +1729,30 @@
}
}
+ @Override
+ public void onDisplayAdded(int displayId) {}
+
+ @Override
+ public void onDisplayRemoved(int displayId) {}
+
+ @Override
public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
updateDefaultDisplayState();
- }
- }
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- synchronized (mLock) {
- final ContentResolver cr = mContext.getContentResolver();
- int brightness = Settings.System.getIntForUser(cr,
- Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
- if (brightness != mBrightness) {
- mBrightness = brightness;
- onBrightnessChangedLocked();
+ // We don't support multiple display blocking zones yet, so only handle
+ // brightness changes for the default display for now.
+ int brightness = getBrightness(displayId);
+ synchronized (mLock) {
+ if (brightness != mBrightness) {
+ mBrightness = brightness;
+ onBrightnessChangedLocked();
+ }
}
}
}
private void restartObserver() {
- final ContentResolver cr = mContext.getContentResolver();
-
if (mRefreshRateInLowZone > 0) {
mShouldObserveDisplayLowChange = hasValidThreshold(
mLowDisplayBrightnessThresholds);
@@ -1729,15 +1773,6 @@
mShouldObserveAmbientHighChange = false;
}
- if (mShouldObserveDisplayLowChange || mShouldObserveDisplayHighChange) {
- // Content Service does not check if an listener has already been registered.
- // To ensure only one listener is registered, force an unregistration first.
- mInjector.unregisterBrightnessObserver(cr, this);
- mInjector.registerBrightnessObserver(cr, this);
- } else {
- mInjector.unregisterBrightnessObserver(cr, this);
- }
-
if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
Resources resources = mContext.getResources();
String lightSensorType = resources.getString(
@@ -1937,6 +1972,15 @@
return mDefaultDisplayState == Display.STATE_ON;
}
+ private int getBrightness(int displayId) {
+ final BrightnessInfo info = mInjector.getBrightnessInfo(displayId);
+ if (info != null) {
+ return BrightnessSynchronizer.brightnessFloatToInt(info.adjustedBrightness);
+ }
+
+ return BRIGHTNESS_INVALID;
+ }
+
private final class LightSensorEventListener implements SensorEventListener {
final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
private float mLastSensorData;
@@ -2248,33 +2292,80 @@
* HBM that are associated with that display. Restrictions are retrieved from
* DisplayManagerInternal but originate in the display-device-config file.
*/
- private static class HbmObserver implements DisplayManager.DisplayListener {
+ public static class HbmObserver implements DisplayManager.DisplayListener {
private final BallotBox mBallotBox;
private final Handler mHandler;
- private final SparseBooleanArray mHbmEnabled = new SparseBooleanArray();
+ private final SparseIntArray mHbmMode = new SparseIntArray();
+ private final SparseBooleanArray mHbmActive = new SparseBooleanArray();
private final Injector mInjector;
+ private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
+ private int mRefreshRateInHbmSunlight;
+ private int mRefreshRateInHbmHdr;
private DisplayManagerInternal mDisplayManagerInternal;
- HbmObserver(Injector injector, BallotBox ballotBox, Handler handler) {
+ HbmObserver(Injector injector, BallotBox ballotBox, Handler handler,
+ DeviceConfigDisplaySettings displaySettings) {
mInjector = injector;
mBallotBox = ballotBox;
mHandler = handler;
+ mDeviceConfigDisplaySettings = displaySettings;
}
public void observe() {
+ mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings.getRefreshRateInHbmSunlight();
+ mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings.getRefreshRateInHbmHdr();
+
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mInjector.registerDisplayListener(this, mHandler,
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
| DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
}
+ /**
+ * @return the refresh to lock to when the device is in high brightness mode for Sunlight.
+ */
+ @VisibleForTesting
+ int getRefreshRateInHbmSunlight() {
+ return mRefreshRateInHbmSunlight;
+ }
+
+ /**
+ * @return the refresh to lock to when the device is in high brightness mode for HDR.
+ */
+ @VisibleForTesting
+ int getRefreshRateInHbmHdr() {
+ return mRefreshRateInHbmHdr;
+ }
+
+ /**
+ * Recalculates the HBM vote when the device config has been changed.
+ */
+ public void onDeviceConfigRefreshRateInHbmSunlightChanged(int refreshRate) {
+ if (refreshRate != mRefreshRateInHbmSunlight) {
+ mRefreshRateInHbmSunlight = refreshRate;
+ onDeviceConfigRefreshRateInHbmChanged();
+ }
+ }
+
+ /**
+ * Recalculates the HBM vote when the device config has been changed.
+ */
+ public void onDeviceConfigRefreshRateInHbmHdrChanged(int refreshRate) {
+ if (refreshRate != mRefreshRateInHbmHdr) {
+ mRefreshRateInHbmHdr = refreshRate;
+ onDeviceConfigRefreshRateInHbmChanged();
+ }
+ }
+
@Override
public void onDisplayAdded(int displayId) {}
@Override
public void onDisplayRemoved(int displayId) {
mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
+ mHbmMode.delete(displayId);
+ mHbmActive.delete(displayId);
}
@Override
@@ -2284,31 +2375,116 @@
// Display no longer there. Assume we'll get an onDisplayRemoved very soon.
return;
}
- final boolean isHbmEnabled =
- info.highBrightnessMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
- if (isHbmEnabled == mHbmEnabled.get(displayId)) {
+
+ final int hbmMode = info.highBrightnessMode;
+ final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF &&
+ info.adjustedBrightness > info.highBrightnessTransitionPoint;
+ if (hbmMode == mHbmMode.get(displayId) &&
+ isHbmActive == mHbmActive.get(displayId)) {
// no change, ignore.
return;
}
- Vote vote = null;
- mHbmEnabled.put(displayId, isHbmEnabled);
- if (isHbmEnabled) {
- final List<RefreshRateLimitation> limits =
- mDisplayManagerInternal.getRefreshRateLimitations(displayId);
- for (int i = 0; limits != null && i < limits.size(); i++) {
- final RefreshRateLimitation limitation = limits.get(i);
- if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
- vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max);
- break;
- }
+ mHbmMode.put(displayId, hbmMode);
+ mHbmActive.put(displayId, isHbmActive);
+ recalculateVotesForDisplay(displayId);
+ }
+
+ private void onDeviceConfigRefreshRateInHbmChanged() {
+ final int[] displayIds = mHbmMode.copyKeys();
+ if (displayIds != null) {
+ for (int id : displayIds) {
+ recalculateVotesForDisplay(id);
}
}
+ }
+
+ private void recalculateVotesForDisplay(int displayId) {
+ Vote vote = null;
+ if (mHbmActive.get(displayId, false)) {
+ final int hbmMode =
+ mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+ if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+ // Device resource properties take priority over DisplayDeviceConfig
+ if (mRefreshRateInHbmSunlight > 0) {
+ vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight,
+ mRefreshRateInHbmSunlight);
+ } else {
+ final List<RefreshRateLimitation> limits =
+ mDisplayManagerInternal.getRefreshRateLimitations(displayId);
+ for (int i = 0; limits != null && i < limits.size(); i++) {
+ final RefreshRateLimitation limitation = limits.get(i);
+ if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
+ vote = Vote.forRefreshRates(limitation.range.min,
+ limitation.range.max);
+ break;
+ }
+ }
+ }
+ } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR &&
+ mRefreshRateInHbmHdr > 0) {
+ // HBM for HDR vote isn't supported through DisplayDeviceConfig yet, so look for
+ // a vote from Device properties
+ vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
+ } else {
+ Slog.w(TAG, "Unexpected HBM mode " + hbmMode + " for display ID " + displayId);
+ }
+
+ }
mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
}
void dumpLocked(PrintWriter pw) {
pw.println(" HbmObserver");
- pw.println(" mHbmEnabled: " + mHbmEnabled);
+ pw.println(" mHbmMode: " + mHbmMode);
+ pw.println(" mHbmActive: " + mHbmActive);
+ pw.println(" mRefreshRateInHbmSunlight: " + mRefreshRateInHbmSunlight);
+ pw.println(" mRefreshRateInHbmHdr: " + mRefreshRateInHbmHdr);
+ }
+ }
+
+ private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
+ private final BallotBox mBallotBox;
+ private final Injector mInjector;
+
+ private @Temperature.ThrottlingStatus int mStatus = -1;
+
+ SkinThermalStatusObserver(Injector injector, BallotBox ballotBox) {
+ mInjector = injector;
+ mBallotBox = ballotBox;
+ }
+
+ @Override
+ public void notifyThrottling(Temperature temp) {
+ mStatus = temp.getStatus();
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "New thermal throttling status "
+ + ", current thermal status = " + mStatus);
+ }
+ final Vote vote;
+ if (mStatus >= Temperature.THROTTLING_CRITICAL) {
+ vote = Vote.forRefreshRates(0f, 60f);
+ } else {
+ vote = null;
+ }
+ mBallotBox.vote(GLOBAL_ID, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
+ }
+
+ public void observe() {
+ IThermalService thermalService = mInjector.getThermalService();
+ if (thermalService == null) {
+ Slog.w(TAG, "Could not observe thermal status. Service not available");
+ return;
+ }
+ try {
+ thermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ }
+ }
+
+ void dumpLocked(PrintWriter writer) {
+ writer.println(" SkinThermalStatusObserver:");
+ writer.println(" mStatus: " + mStatus);
}
}
@@ -2381,6 +2557,29 @@
return refreshRate;
}
+ public int getRefreshRateInHbmSunlight() {
+ final int defaultRefreshRateInHbmSunlight =
+ mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInHbmSunlight);
+
+ final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
+ defaultRefreshRateInHbmSunlight);
+
+ return refreshRate;
+ }
+
+ public int getRefreshRateInHbmHdr() {
+ final int defaultRefreshRateInHbmHdr =
+ mContext.getResources().getInteger(R.integer.config_defaultRefreshRateInHbmHdr);
+
+ final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
+ defaultRefreshRateInHbmHdr);
+
+ return refreshRate;
+ }
+
/*
* Return null if no such property
*/
@@ -2420,6 +2619,15 @@
.sendToTarget();
mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0)
.sendToTarget();
+
+ final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight();
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED,
+ refreshRateInHbmSunlight, 0)
+ .sendToTarget();
+
+ final int refreshRateInHbmHdr = getRefreshRateInHbmHdr();
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0)
+ .sendToTarget();
}
private int[] getIntArrayProperty(String prop) {
@@ -2451,19 +2659,11 @@
}
interface Injector {
- // TODO: brightnessfloat: change this to the float setting
- Uri DISPLAY_BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
@NonNull
DeviceConfigInterface getDeviceConfig();
- void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer);
-
- void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer);
-
void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer);
@@ -2473,6 +2673,8 @@
BrightnessInfo getBrightnessInfo(int displayId);
boolean isDozeState(Display d);
+
+ IThermalService getThermalService();
}
@VisibleForTesting
@@ -2491,19 +2693,6 @@
}
@Override
- public void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- cr.registerContentObserver(DISPLAY_BRIGHTNESS_URI, false /*notifyDescendants*/,
- observer, UserHandle.USER_SYSTEM);
- }
-
- @Override
- public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- cr.unregisterContentObserver(observer);
- }
-
- @Override
public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
@@ -2533,6 +2722,12 @@
return Display.isDozeState(d.getState());
}
+ @Override
+ public IThermalService getThermalService() {
+ return IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ }
+
private DisplayManager getDisplayManager() {
if (mDisplayManager == null) {
mDisplayManager = mContext.getSystemService(DisplayManager.class);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index abbe13a..1224902 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -125,6 +125,7 @@
private static final int MSG_IGNORE_PROXIMITY = 8;
private static final int MSG_STOP = 9;
private static final int MSG_UPDATE_BRIGHTNESS = 10;
+ private static final int MSG_UPDATE_RBC = 11;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -422,13 +423,13 @@
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
private float mTemporaryAutoBrightnessAdjustment;
- // Whether a reduce bright colors (rbc) change has been initiated by the user. We want to
- // retain the current backlight level when rbc is toggled, since rbc additionally makes the
- // screen appear dimmer using screen colors rather than backlight levels, and therefore we
- // don't actually want to compensate for this by then in/decreasing the backlight when
- // toggling this feature.
+ // Whether reduce bright colors (rbc) has been turned on, or a change in strength has been
+ // requested. We want to retain the current backlight level when rbc is toggled, since rbc
+ // additionally makes the screen appear dimmer using screen colors rather than backlight levels,
+ // and therefore we don't actually want to compensate for this by then in/decreasing the
+ // backlight when toggling this feature.
// This should be false during system start up.
- private boolean mPendingUserRbcChange;
+ private boolean mPendingRbcOnOrChanged = false;
// Animators.
private ObjectAnimator mColorFadeOnAnimator;
@@ -564,23 +565,35 @@
@Override
public void onReduceBrightColorsActivationChanged(boolean activated,
boolean userInitiated) {
- applyReduceBrightColorsSplineAdjustment(userInitiated);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, activated);
+
}
@Override
public void onReduceBrightColorsStrengthChanged(int strength) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ true, /* justActivated= */ false);
}
});
if (active) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, /* justActivated= */ false);
}
} else {
mCdsi = null;
}
}
- private void applyReduceBrightColorsSplineAdjustment(boolean userInitiated) {
+ private void applyReduceBrightColorsSplineAdjustment(
+ boolean rbcStrengthChanged, boolean justActivated) {
+ final int strengthChanged = rbcStrengthChanged ? 1 : 0;
+ final int activated = justActivated ? 1 : 0;
+ mHandler.obtainMessage(MSG_UPDATE_RBC, strengthChanged, activated).sendToTarget();
+ sendUpdatePowerState();
+ }
+
+ private void handleRbcChanged(boolean strengthChanged, boolean justActivated) {
if (mBrightnessMapper == null) {
Log.w(TAG, "No brightness mapping available to recalculate splines");
return;
@@ -591,8 +604,13 @@
adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
}
mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits);
- mPendingUserRbcChange = userInitiated;
- sendUpdatePowerState();
+
+ mPendingRbcOnOrChanged = strengthChanged || justActivated;
+
+ // Reset model if strength changed OR rbc is turned off
+ if (strengthChanged || !justActivated && mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.resetShortTermModel();
+ }
}
/**
@@ -926,7 +944,8 @@
private void reloadReduceBrightColours() {
if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, /* justActivated= */ false);
}
}
@@ -1259,10 +1278,6 @@
putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true);
}
- // We save the brightness info *after* the brightness setting has been changed so that
- // the brightness info reflects the latest value.
- saveBrightnessInfo(getScreenBrightnessSetting());
-
// Apply dimming by at least some minimum amount when user activity
// timeout is about to expire.
if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -1393,6 +1408,11 @@
hadUserBrightnessPoint);
}
+ // We save the brightness info *after* the brightness setting has been changed and
+ // adjustments made so that the brightness info reflects the latest value.
+ saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+ } else {
+ saveBrightnessInfo(getScreenBrightnessSetting());
}
// Log any changes to what is currently driving the brightness setting.
@@ -1509,18 +1529,27 @@
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
mCachedBrightnessInfo.brightness,
+ mCachedBrightnessInfo.adjustedBrightness,
mCachedBrightnessInfo.brightnessMin,
mCachedBrightnessInfo.brightnessMax,
- mCachedBrightnessInfo.hbmMode);
+ mCachedBrightnessInfo.hbmMode,
+ mCachedBrightnessInfo.highBrightnessTransitionPoint);
}
}
private void saveBrightnessInfo(float brightness) {
+ saveBrightnessInfo(brightness, brightness);
+ }
+
+ private void saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
mCachedBrightnessInfo.brightness = brightness;
+ mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness;
mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin();
mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax();
mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode();
+ mCachedBrightnessInfo.highBrightnessTransitionPoint =
+ mHbmController.getTransitionPoint();
}
}
@@ -2062,21 +2091,24 @@
return true;
}
+ // We want to return true if the user has set the screen brightness.
+ // If they have just turned RBC on (and therefore added that interaction to the curve),
+ // or changed the brightness another way, then we should return true.
private boolean updateUserSetScreenBrightness() {
- final boolean brightnessSplineChanged = mPendingUserRbcChange;
- if (mPendingUserRbcChange && !Float.isNaN(mCurrentScreenBrightnessSetting)) {
+ final boolean treatAsIfUserChanged = mPendingRbcOnOrChanged;
+ if (treatAsIfUserChanged && !Float.isNaN(mCurrentScreenBrightnessSetting)) {
mLastUserSetScreenBrightness = mCurrentScreenBrightnessSetting;
}
- mPendingUserRbcChange = false;
+ mPendingRbcOnOrChanged = false;
if ((Float.isNaN(mPendingScreenBrightnessSetting)
|| mPendingScreenBrightnessSetting < 0.0f)) {
- return brightnessSplineChanged;
+ return treatAsIfUserChanged;
}
if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- return brightnessSplineChanged;
+ return treatAsIfUserChanged;
}
setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
@@ -2195,6 +2227,18 @@
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
+ synchronized (mCachedBrightnessInfo) {
+ pw.println(" mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness);
+ pw.println(" mCachedBrightnessInfo.adjustedBrightness=" +
+ mCachedBrightnessInfo.adjustedBrightness);
+ pw.println(" mCachedBrightnessInfo.brightnessMin=" +
+ mCachedBrightnessInfo.brightnessMin);
+ pw.println(" mCachedBrightnessInfo.brightnessMax=" +
+ mCachedBrightnessInfo.brightnessMax);
+ pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode);
+ pw.println(" mCachedBrightnessInfo.highBrightnessTransitionPoint=" +
+ mCachedBrightnessInfo.highBrightnessTransitionPoint);
+ }
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2406,6 +2450,12 @@
}
handleSettingsChange(false /*userSwitch*/);
break;
+
+ case MSG_UPDATE_RBC:
+ final int strengthChanged = msg.arg1;
+ final int justActivated = msg.arg2;
+ handleRbcChanged(strengthChanged == 1, justActivated == 1);
+ break;
}
}
}
@@ -2606,8 +2656,10 @@
static class CachedBrightnessInfo {
public float brightness;
+ public float adjustedBrightness;
public float brightnessMin;
public float brightnessMax;
public int hbmMode;
+ public float highBrightnessTransitionPoint;
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 6af1923..147050c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -411,7 +411,7 @@
* Updates the state of the screen and backlight asynchronously on a separate thread.
*/
private final class PhotonicModulator extends Thread {
- private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off
+ private static final int INITIAL_SCREEN_STATE = Display.STATE_UNKNOWN;
private static final float INITIAL_BACKLIGHT_FLOAT = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private final Object mLock = new Object();
@@ -494,7 +494,9 @@
if (!backlightChanged) {
mBacklightChangeInProgress = false;
}
- if (!stateChanged && !backlightChanged) {
+ boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState);
+ boolean changed = stateChanged || backlightChanged;
+ if (!valid || !changed) {
try {
mLock.wait();
} catch (InterruptedException ex) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 2791f6a..1e1cfeb 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -59,6 +59,9 @@
private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f;
+ @VisibleForTesting
+ static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
+
private final float mBrightnessMin;
private final float mBrightnessMax;
private final Handler mHandler;
@@ -214,6 +217,14 @@
return mHbmMode;
}
+ float getTransitionPoint() {
+ if (deviceSupportsHbm()) {
+ return mHbmData.transitionPoint;
+ } else {
+ return HBM_TRANSITION_POINT_INVALID;
+ }
+ }
+
void stop() {
registerHdrListener(null /*displayToken*/);
mSkinThermalStatusObserver.stopObserving();
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index fa33338..03e421b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -209,6 +209,12 @@
*/
private AtomicBoolean mIsPendingIntentCancelled = new AtomicBoolean(false);
+ /**
+ * True if a permissions query has been issued and is being processed. Used to prevent too many
+ * queries from being issued by a single client at once.
+ */
+ private AtomicBoolean mIsPermQueryIssued = new AtomicBoolean(false);
+
/*
* True if the application creating the client has the ACCESS_CONTEXT_HUB permission.
*/
@@ -240,11 +246,11 @@
private final IContextHubTransactionCallback mQueryPermsCallback =
new IContextHubTransactionCallback.Stub() {
@Override
- public void onTransactionComplete(int result) {
- }
+ public void onTransactionComplete(int result) {}
@Override
public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ mIsPermQueryIssued.set(false);
if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
Log.e(TAG, "Permissions query failed, but still received nanoapp state");
} else if (nanoAppStateList != null) {
@@ -656,9 +662,11 @@
* communicated with in the past.
*/
private void checkNanoappPermsAsync() {
- ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
- mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
- mTransactionManager.addTransaction(transaction);
+ if (!mIsPermQueryIssued.getAndSet(true)) {
+ ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+ mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+ mTransactionManager.addTransaction(transaction);
+ }
}
private int updateNanoAppAuthState(
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index f3dcfbb..9956da2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -16,6 +16,7 @@
package com.android.server.location.gnss;
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.location.provider.ProviderProperties.ACCURACY_FINE;
import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
@@ -43,6 +44,8 @@
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_MODE_STANDALONE;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_RECURRENCE_PERIODIC;
+import static java.lang.Math.abs;
+import static java.lang.Math.max;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.AlarmManager;
@@ -85,6 +88,7 @@
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
import android.util.TimeUtils;
@@ -103,7 +107,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -156,6 +162,10 @@
private static final long LOCATION_UPDATE_DURATION_MILLIS = 10 * 1000;
// Update duration extension multiplier for emergency REQUEST_LOCATION.
private static final int EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3;
+ // maximum length gnss batching may go for (1 day)
+ private static final int MIN_BATCH_INTERVAL_MS = (int) DateUtils.SECOND_IN_MILLIS;
+ private static final long MAX_BATCH_LENGTH_MS = DateUtils.DAY_IN_MILLIS;
+ private static final long MAX_BATCH_TIMESTAMP_DELTA_MS = 500;
// Threadsafe class to hold stats reported in the Extras Bundle
private static class LocationExtras {
@@ -245,6 +255,7 @@
private boolean mShutdown;
private boolean mStarted;
private boolean mBatchingStarted;
+ private AlarmManager.OnAlarmListener mBatchingAlarm;
private long mStartedChangedElapsedRealtime;
private int mFixInterval = 1000;
@@ -845,18 +856,15 @@
mFixInterval = Integer.MAX_VALUE;
}
- // requested batch size, or zero to disable batching
- long batchSize =
- mBatchingEnabled ? mProviderRequest.getMaxUpdateDelayMillis() / Math.max(
- mFixInterval, 1) : 0;
- if (batchSize < getBatchSize()) {
- batchSize = 0;
- }
+ int batchIntervalMs = max(mFixInterval, MIN_BATCH_INTERVAL_MS);
+ long batchLengthMs = Math.min(mProviderRequest.getMaxUpdateDelayMillis(),
+ MAX_BATCH_LENGTH_MS);
// apply request to GPS engine
- if (batchSize > 0) {
+ if (mBatchingEnabled && batchLengthMs / 2 >= batchIntervalMs) {
stopNavigating();
- startBatching();
+ mFixInterval = batchIntervalMs;
+ startBatching(batchLengthMs);
} else {
stopBatching();
@@ -875,7 +883,7 @@
if (mFixInterval >= NO_FIX_TIMEOUT) {
// set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
// and our fix interval is not short
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG,
mTimeoutListener, mHandler);
}
@@ -1066,7 +1074,7 @@
// set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
// and our fix interval is not short
if (mFixInterval >= NO_FIX_TIMEOUT) {
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG, mTimeoutListener,
mHandler);
}
@@ -1090,12 +1098,37 @@
mAlarmManager.cancel(mWakeupListener);
}
- private void startBatching() {
+ private void startBatching(long batchLengthMs) {
+ long batchSize = batchLengthMs / mFixInterval;
+
if (DEBUG) {
- Log.d(TAG, "startBatching " + mFixInterval);
+ Log.d(TAG, "startBatching " + mFixInterval + " " + batchLengthMs);
}
if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), true)) {
mBatchingStarted = true;
+
+ if (batchSize < getBatchSize()) {
+ // if the batch size is smaller than the hardware batch size, use an alarm to flush
+ // locations as appropriate
+ mBatchingAlarm = () -> {
+ boolean flush = false;
+ synchronized (mLock) {
+ if (mBatchingAlarm != null) {
+ flush = true;
+ mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + batchLengthMs, TAG,
+ mBatchingAlarm, FgThread.getHandler());
+ }
+ }
+
+ if (flush) {
+ mGnssNative.flushBatch();
+ }
+ };
+ mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + batchLengthMs, TAG,
+ mBatchingAlarm, FgThread.getHandler());
+ }
} else {
Log.e(TAG, "native_start_batch failed in startBatching()");
}
@@ -1104,6 +1137,10 @@
private void stopBatching() {
if (DEBUG) Log.d(TAG, "stopBatching");
if (mBatchingStarted) {
+ if (mBatchingAlarm != null) {
+ mAlarmManager.cancel(mBatchingAlarm);
+ mBatchingAlarm = null;
+ }
mGnssNative.stopBatch();
mBatchingStarted = false;
}
@@ -1120,7 +1157,7 @@
// stop GPS until our next fix interval arrives
stopNavigating();
long now = SystemClock.elapsedRealtime();
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
mWakeupListener, mHandler);
}
@@ -1485,16 +1522,50 @@
Log.d(TAG, "Location batch of size " + locations.length + " reported");
}
+ if (locations.length > 0) {
+ // attempt to fix up timestamps if necessary
+ if (locations.length > 1) {
+ // check any realtimes outside of expected bounds
+ boolean fixRealtime = false;
+ for (int i = locations.length - 2; i >= 0; i--) {
+ long timeDeltaMs = locations[i + 1].getTime() - locations[i].getTime();
+ long realtimeDeltaMs = locations[i + 1].getElapsedRealtimeMillis()
+ - locations[i].getElapsedRealtimeMillis();
+ if (abs(timeDeltaMs - realtimeDeltaMs) > MAX_BATCH_TIMESTAMP_DELTA_MS) {
+ fixRealtime = true;
+ break;
+ }
+ }
+
+ if (fixRealtime) {
+ // sort for monotonically increasing time before fixing realtime - realtime will
+ // thus also be monotonically increasing
+ Arrays.sort(locations,
+ Comparator.comparingLong(Location::getTime));
+
+ long expectedDeltaMs =
+ locations[locations.length - 1].getTime()
+ - locations[locations.length - 1].getElapsedRealtimeMillis();
+ for (int i = locations.length - 2; i >= 0; i--) {
+ locations[i].setElapsedRealtimeNanos(
+ MILLISECONDS.toNanos(
+ max(locations[i].getTime() - expectedDeltaMs, 0)));
+ }
+ } else {
+ // sort for monotonically increasing realttime
+ Arrays.sort(locations,
+ Comparator.comparingLong(Location::getElapsedRealtimeNanos));
+ }
+ }
+
+ reportLocation(LocationResult.wrap(locations).validate());
+ }
+
Runnable[] listeners;
synchronized (mLock) {
listeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
}
-
- if (locations.length > 0) {
- reportLocation(LocationResult.wrap(locations).validate());
- }
-
for (Runnable listener : listeners) {
listener.run();
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 2227506..4d9253e 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -444,12 +444,11 @@
capabilities = networkAttributes.mCapabilities;
Log.i(TAG, String.format(
"updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
- + ", apn: %s, availableNetworkCount: %d",
+ + ", availableNetworkCount: %d",
agpsDataConnStateAsString(),
isConnected,
network,
capabilities,
- apn,
mAvailableNetworkAttributes.size()));
if (native_is_agps_ril_supported()) {
diff --git a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
index a47c48f..2df2101 100644
--- a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
@@ -100,7 +100,7 @@
return false;
}
- return mAppOps.checkOpNoThrow(permissionLevel, identity);
+ return mAppOps.checkOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity);
}
protected abstract boolean hasPermission(String permission, CallerIdentity callerIdentity);
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index d882705..83de0b3 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -243,15 +243,24 @@
intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
}
+ // send() SHOULD only run the completion callback if it completes successfully. however,
+ // b/199464864 (which could not be fixed in the S timeframe) means that it's possible
+ // for send() to throw an exception AND run the completion callback. if this happens, we
+ // would over-release the wakelock... we take matters into our own hands to ensure that
+ // the completion callback can only be run if send() completes successfully. this means
+ // the completion callback may be run inline - but as we've never specified what thread
+ // the callback is run on, this is fine.
+ GatedCallback gatedCallback = new GatedCallback(onCompleteCallback);
+
mPendingIntent.send(
mContext,
0,
intent,
- onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run()
- : null,
+ (pI, i, rC, rD, rE) -> gatedCallback.run(),
null,
null,
options.toBundle());
+ gatedCallback.allow();
}
@Override
@@ -1078,7 +1087,7 @@
}
private void onTransportFailure(Exception e) {
- if (e instanceof RemoteException) {
+ if (e instanceof PendingIntent.CanceledException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
synchronized (mLock) {
remove();
@@ -1960,11 +1969,6 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
- if (mDelayedRegister != null) {
- mAlarmHelper.cancel(mDelayedRegister);
- mDelayedRegister = null;
- }
-
// calculate how long the new request should be delayed before sending it off to the
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
@@ -1997,8 +2001,8 @@
public void onAlarm() {
synchronized (mLock) {
if (mDelayedRegister == this) {
- setProviderRequest(newRequest);
mDelayedRegister = null;
+ setProviderRequest(newRequest);
}
}
}
@@ -2025,6 +2029,11 @@
@GuardedBy("mLock")
void setProviderRequest(ProviderRequest request) {
+ if (mDelayedRegister != null) {
+ mAlarmHelper.cancel(mDelayedRegister);
+ mDelayedRegister = null;
+ }
+
EVENT_LOG.logProviderUpdateRequest(mName, request);
mProvider.getController().setRequest(request);
@@ -2734,4 +2743,49 @@
}
}
}
+
+ private static class GatedCallback implements Runnable {
+
+ private @Nullable Runnable mCallback;
+
+ @GuardedBy("this")
+ private boolean mGate;
+ @GuardedBy("this")
+ private boolean mRun;
+
+ GatedCallback(Runnable callback) {
+ mCallback = callback;
+ }
+
+ public void allow() {
+ Runnable callback = null;
+ synchronized (this) {
+ mGate = true;
+ if (mRun && mCallback != null) {
+ callback = mCallback;
+ mCallback = null;
+ }
+ }
+
+ if (callback != null) {
+ callback.run();
+ }
+ }
+
+ @Override
+ public void run() {
+ Runnable callback = null;
+ synchronized (this) {
+ mRun = true;
+ if (mGate && mCallback != null) {
+ callback = mCallback;
+ mCallback = null;
+ }
+ }
+
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 9f02c3c..a57d7db 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -903,26 +903,8 @@
if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
synchronized (mLock) {
- boolean wasA2dpOn = mGlobalBluetoothA2dpOn;
mActiveBluetoothDevice = btDevice;
mGlobalBluetoothA2dpOn = btDevice != null;
- if (wasA2dpOn != mGlobalBluetoothA2dpOn) {
- Slog.d(TAG, "GlobalBluetoothA2dpOn is changed to '"
- + mGlobalBluetoothA2dpOn + "'");
- UserRecord userRecord = mUserRecords.get(mCurrentUserId);
- if (userRecord != null) {
- for (ClientRecord cr : userRecord.mClientRecords) {
- // mSelectedRouteId will be null for BT and phone speaker.
- if (cr.mSelectedRouteId == null) {
- try {
- cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn);
- } catch (RemoteException e) {
- // Ignore exception
- }
- }
- }
- }
- }
}
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 5de5fd3..2ca057d 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -255,6 +255,7 @@
import com.android.internal.util.StatLogger;
import com.android.internal.util.XmlUtils;
import com.android.net.module.util.NetworkIdentityUtils;
+import com.android.net.module.util.NetworkStatsUtils;
import com.android.net.module.util.PermissionUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -2024,7 +2025,7 @@
for (final NetworkStateSnapshot snapshot : snapshots) {
mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot));
- // Policies matched by NPMS only match by subscriber ID or by ssid. Thus subtype
+ // Policies matched by NPMS only match by subscriber ID or by network ID. Thus subtype
// in the object created here is never used and its value doesn't matter, so use
// NETWORK_TYPE_UNKNOWN.
final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
@@ -2388,7 +2389,8 @@
templateMeteredness = readIntAttribute(in, ATTR_TEMPLATE_METERED);
} else {
- subscriberIdMatchRule = NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT;
+ subscriberIdMatchRule =
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
if (templateType == MATCH_MOBILE) {
Log.d(TAG, "Update template match rule from mobile to carrier and"
+ " force to metered");
@@ -2451,12 +2453,20 @@
} else {
inferred = false;
}
- final NetworkTemplate template = new NetworkTemplate(templateType,
- subscriberId, new String[] { subscriberId },
- networkId, templateMeteredness, NetworkStats.ROAMING_ALL,
- NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL,
- NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule);
- if (template.isPersistable()) {
+ final NetworkTemplate.Builder builder =
+ new NetworkTemplate.Builder(templateType)
+ .setMeteredness(templateMeteredness);
+ if (subscriberIdMatchRule
+ == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT) {
+ final ArraySet<String> ids = new ArraySet<>();
+ ids.add(subscriberId);
+ builder.setSubscriberIds(ids);
+ }
+ if (networkId != null) {
+ builder.setWifiNetworkKeys(Set.of(networkId));
+ }
+ final NetworkTemplate template = builder.build();
+ if (NetworkPolicy.isTemplatePersistable(template)) {
mNetworkPolicy.put(template, new NetworkPolicy(template, cycleRule,
warningBytes, limitBytes, lastWarningSnooze,
lastLimitSnooze, metered, inferred));
@@ -2605,35 +2615,39 @@
* into {@link WifiConfiguration}.
*/
private void upgradeWifiMeteredOverride() {
- final ArrayMap<String, Boolean> wifiNetworkIds = new ArrayMap<>();
+ final ArrayMap<String, Boolean> wifiNetworkKeys = new ArrayMap<>();
synchronized (mNetworkPoliciesSecondLock) {
for (int i = 0; i < mNetworkPolicy.size();) {
final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
if (policy.template.getMatchRule() == NetworkTemplate.MATCH_WIFI
&& !policy.inferred) {
mNetworkPolicy.removeAt(i);
- wifiNetworkIds.put(policy.template.getNetworkId(), policy.metered);
+ final Set<String> keys = policy.template.getWifiNetworkKeys();
+ wifiNetworkKeys.put(keys.isEmpty() ? null : keys.iterator().next(),
+ policy.metered);
} else {
i++;
}
}
}
- if (wifiNetworkIds.isEmpty()) {
+ if (wifiNetworkKeys.isEmpty()) {
return;
}
final WifiManager wm = mContext.getSystemService(WifiManager.class);
final List<WifiConfiguration> configs = wm.getConfiguredNetworks();
for (int i = 0; i < configs.size(); ++i) {
final WifiConfiguration config = configs.get(i);
- final String networkId = resolveNetworkId(config);
- final Boolean metered = wifiNetworkIds.get(networkId);
- if (metered != null) {
- Slog.d(TAG, "Found network " + networkId + "; upgrading metered hint");
- config.meteredOverride = metered
- ? WifiConfiguration.METERED_OVERRIDE_METERED
- : WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
- wm.updateNetwork(config);
+ for (String key : config.getAllPersistableNetworkKeys()) {
+ final Boolean metered = wifiNetworkKeys.get(key);
+ if (metered != null) {
+ Slog.d(TAG, "Found network " + key + "; upgrading metered hint");
+ config.meteredOverride = metered
+ ? WifiConfiguration.METERED_OVERRIDE_METERED
+ : WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
+ wm.updateNetwork(config);
+ break;
+ }
}
}
@@ -2663,7 +2677,7 @@
for (int i = 0; i < mNetworkPolicy.size(); i++) {
final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
final NetworkTemplate template = policy.template;
- if (!template.isPersistable()) continue;
+ if (!NetworkPolicy.isTemplatePersistable(template)) continue;
out.startTag(null, TAG_NETWORK_POLICY);
writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
@@ -2671,11 +2685,13 @@
if (subscriberId != null) {
out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
}
- writeIntAttribute(out, ATTR_SUBSCRIBER_ID_MATCH_RULE,
- template.getSubscriberIdMatchRule());
- final String networkId = template.getNetworkId();
- if (networkId != null) {
- out.attribute(null, ATTR_NETWORK_ID, networkId);
+ final int subscriberIdMatchRule = template.getSubscriberIds().isEmpty()
+ ? NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+ : NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
+ writeIntAttribute(out, ATTR_SUBSCRIBER_ID_MATCH_RULE, subscriberIdMatchRule);
+ if (!template.getWifiNetworkKeys().isEmpty()) {
+ out.attribute(null, ATTR_NETWORK_ID,
+ template.getWifiNetworkKeys().iterator().next());
}
writeIntAttribute(out, ATTR_TEMPLATE_METERED,
template.getMeteredness());
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index bccc52f..ddaaa1e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1536,7 +1536,7 @@
@Override
public void onNullBinding(ComponentName name) {
Slog.v(TAG, "onNullBinding() called with: name = [" + name + "]");
- mServicesBound.remove(servicesBindingTag);
+ mContext.unbindService(this);
}
};
if (!mContext.bindServiceAsUser(intent,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b54e8f9..7ba0f04 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4363,8 +4363,7 @@
final int userId = r.getSbn().getUserId();
if (userId != info.userid && userId != UserHandle.USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
- throw new SecurityException("Disallowed call from listener: "
- + info.service);
+ continue;
}
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
r.getSbn().getPackageName(), r.getSbn().getTag(),
@@ -4431,8 +4430,7 @@
final int userId = r.getSbn().getUserId();
if (userId != info.userid && userId != UserHandle.USER_ALL
&& !mUserProfiles.isCurrentProfile(userId)) {
- throw new SecurityException("Disallowed call from listener: "
- + info.service);
+ continue;
}
seen.add(r);
if (!r.isSeen()) {
@@ -5399,6 +5397,7 @@
== IMPORTANCE_NONE) {
cancelNotificationsFromListener(token, new String[]{r.getKey()});
} else {
+ r.setPendingLogUpdate(true);
needsSort = true;
}
}
@@ -7451,15 +7450,21 @@
sentAccessibilityEvent = true;
}
if (DBG) Slog.v(TAG, "Interrupting!");
+ boolean isInsistentUpdate = isInsistentUpdate(record);
if (hasValidSound) {
- if (isInCall()) {
- playInCallNotification();
+ if (isInsistentUpdate) {
+ // don't reset insistent sound, it's jarring
beep = true;
} else {
- beep = playSound(record, soundUri);
- }
- if(beep) {
- mSoundNotificationKey = key;
+ if (isInCall()) {
+ playInCallNotification();
+ beep = true;
+ } else {
+ beep = playSound(record, soundUri);
+ }
+ if (beep) {
+ mSoundNotificationKey = key;
+ }
}
}
@@ -7467,9 +7472,13 @@
mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_SILENT;
if (!isInCall() && hasValidVibrate && !ringerModeSilent) {
- buzz = playVibration(record, vibration, hasValidSound);
- if(buzz) {
- mVibrateNotificationKey = key;
+ if (isInsistentUpdate) {
+ buzz = true;
+ } else {
+ buzz = playVibration(record, vibration, hasValidSound);
+ if (buzz) {
+ mVibrateNotificationKey = key;
+ }
}
}
} else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
@@ -7573,6 +7582,19 @@
}
@GuardedBy("mNotificationLock")
+ boolean isInsistentUpdate(final NotificationRecord record) {
+ return (Objects.equals(record.getKey(), mSoundNotificationKey)
+ || Objects.equals(record.getKey(), mVibrateNotificationKey))
+ && isCurrentlyInsistent();
+ }
+
+ @GuardedBy("mNotificationLock")
+ boolean isCurrentlyInsistent() {
+ return isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
+ || isLoopingRingtoneNotification(mNotificationsByKey.get(mVibrateNotificationKey));
+ }
+
+ @GuardedBy("mNotificationLock")
boolean shouldMuteNotificationLocked(final NotificationRecord record) {
// Suppressed because it's a silent update
final Notification notification = record.getNotification();
@@ -7611,10 +7633,8 @@
return true;
}
- // A looping ringtone, such as an incoming call is playing
- if (isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
- || isLoopingRingtoneNotification(
- mNotificationsByKey.get(mVibrateNotificationKey))) {
+ // A different looping ringtone, such as an incoming call is playing
+ if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
return true;
}
@@ -8038,64 +8058,151 @@
}
}
+ static class NotificationRecordExtractorData {
+ // Class that stores any field in a NotificationRecord that can change via an extractor.
+ // Used to cache previous data used in a sort.
+ int mPosition;
+ int mVisibility;
+ boolean mShowBadge;
+ boolean mAllowBubble;
+ boolean mIsBubble;
+ NotificationChannel mChannel;
+ String mGroupKey;
+ ArrayList<String> mOverridePeople;
+ ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ Integer mUserSentiment;
+ Integer mSuppressVisually;
+ ArrayList<Notification.Action> mSystemSmartActions;
+ ArrayList<CharSequence> mSmartReplies;
+ int mImportance;
+
+ // These fields may not trigger a reranking but diffs here may be logged.
+ float mRankingScore;
+ boolean mIsConversation;
+
+ NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
+ boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
+ ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
+ Integer userSentiment, Integer suppressVisually,
+ ArrayList<Notification.Action> systemSmartActions,
+ ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
+ boolean isConversation) {
+ mPosition = position;
+ mVisibility = visibility;
+ mShowBadge = showBadge;
+ mAllowBubble = allowBubble;
+ mIsBubble = isBubble;
+ mChannel = channel;
+ mGroupKey = groupKey;
+ mOverridePeople = overridePeople;
+ mSnoozeCriteria = snoozeCriteria;
+ mUserSentiment = userSentiment;
+ mSuppressVisually = suppressVisually;
+ mSystemSmartActions = systemSmartActions;
+ mSmartReplies = smartReplies;
+ mImportance = importance;
+ mRankingScore = rankingScore;
+ mIsConversation = isConversation;
+ }
+
+ // Returns whether the provided NotificationRecord differs from the cached data in any way.
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || mVisibility != r.getPackageVisibilityOverride()
+ || mShowBadge != r.canShowBadge()
+ || mAllowBubble != r.canBubble()
+ || mIsBubble != r.getNotification().isBubbleNotification()
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance();
+ }
+
+ // Returns whether the NotificationRecord has a change from this data for which we should
+ // log an update. This method specifically targets fields that may be changed via
+ // adjustments from the assistant.
+ //
+ // Fields here are the union of things in NotificationRecordLogger.shouldLogReported
+ // and NotificationRecord.applyAdjustments.
+ //
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance()
+ || !r.rankingScoreMatches(mRankingScore)
+ || mIsConversation != r.isConversation();
+ }
+ }
+
void handleRankingSort() {
if (mRankingHelper == null) return;
synchronized (mNotificationLock) {
final int N = mNotificationList.size();
// Any field that can change via one of the extractors needs to be added here.
- ArrayList<String> orderBefore = new ArrayList<>(N);
- int[] visibilities = new int[N];
- boolean[] showBadges = new boolean[N];
- boolean[] allowBubbles = new boolean[N];
- boolean[] isBubble = new boolean[N];
- ArrayList<NotificationChannel> channelBefore = new ArrayList<>(N);
- ArrayList<String> groupKeyBefore = new ArrayList<>(N);
- ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
- ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
- ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
- ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
- ArrayList<ArrayList<Notification.Action>> systemSmartActionsBefore = new ArrayList<>(N);
- ArrayList<ArrayList<CharSequence>> smartRepliesBefore = new ArrayList<>(N);
- int[] importancesBefore = new int[N];
+ ArrayMap<String, NotificationRecordExtractorData> extractorDataBefore =
+ new ArrayMap<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
- orderBefore.add(r.getKey());
- visibilities[i] = r.getPackageVisibilityOverride();
- showBadges[i] = r.canShowBadge();
- allowBubbles[i] = r.canBubble();
- isBubble[i] = r.getNotification().isBubbleNotification();
- channelBefore.add(r.getChannel());
- groupKeyBefore.add(r.getGroupKey());
- overridePeopleBefore.add(r.getPeopleOverride());
- snoozeCriteriaBefore.add(r.getSnoozeCriteria());
- userSentimentBefore.add(r.getUserSentiment());
- suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
- systemSmartActionsBefore.add(r.getSystemGeneratedSmartActions());
- smartRepliesBefore.add(r.getSmartReplies());
- importancesBefore[i] = r.getImportance();
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ i,
+ r.getPackageVisibilityOverride(),
+ r.canShowBadge(),
+ r.canBubble(),
+ r.getNotification().isBubbleNotification(),
+ r.getChannel(),
+ r.getGroupKey(),
+ r.getPeopleOverride(),
+ r.getSnoozeCriteria(),
+ r.getUserSentiment(),
+ r.getSuppressedVisualEffects(),
+ r.getSystemGeneratedSmartActions(),
+ r.getSmartReplies(),
+ r.getImportance(),
+ r.getRankingScore(),
+ r.isConversation());
+ extractorDataBefore.put(r.getKey(), extractorData);
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
- if (!orderBefore.get(i).equals(r.getKey())
- || visibilities[i] != r.getPackageVisibilityOverride()
- || showBadges[i] != r.canShowBadge()
- || allowBubbles[i] != r.canBubble()
- || isBubble[i] != r.getNotification().isBubbleNotification()
- || !Objects.equals(channelBefore.get(i), r.getChannel())
- || !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
- || !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
- || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
- || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
- || !Objects.equals(suppressVisuallyBefore.get(i),
- r.getSuppressedVisualEffects())
- || !Objects.equals(systemSmartActionsBefore.get(i),
- r.getSystemGeneratedSmartActions())
- || !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies())
- || importancesBefore[i] != r.getImportance()) {
+ if (!extractorDataBefore.containsKey(r.getKey())) {
+ // This shouldn't happen given that we just built this with all the
+ // notifications, but check just to be safe.
+ continue;
+ }
+ if (extractorDataBefore.get(r.getKey()).hasDiffForRankingLocked(r, i)) {
mHandler.scheduleSendRankingUpdate();
- return;
+ }
+
+ // If this notification is one for which we wanted to log an update, and
+ // sufficient relevant bits are different, log update.
+ if (r.hasPendingLogUpdate()) {
+ // We need to acquire the previous data associated with this specific
+ // notification, as the one at the current index may be unrelated if
+ // notification order has changed.
+ NotificationRecordExtractorData prevData = extractorDataBefore.get(r.getKey());
+ if (prevData.hasDiffForLoggingLocked(r, i)) {
+ mNotificationRecordLogger.logNotificationAdjusted(r, i, 0,
+ getGroupInstanceId(r.getSbn().getGroupKey()));
+ }
+
+ // Remove whether there was a diff or not; we've sorted the key, so if it
+ // turns out there was nothing to log, that's fine too.
+ r.setPendingLogUpdate(false);
}
}
}
@@ -8758,10 +8865,22 @@
void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
+ if (listener == null) {
+ return;
+ }
+ String listenerName = listener.component.toShortString();
if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
return;
}
+ synchronized (mNotificationLock) {
+ final NotificationRecord r = findInCurrentAndSnoozedNotificationByKeyLocked(key);
+ if (r == null) {
+ return;
+ }
+ if (!listener.enabledAndUserMatches(r.getSbn().getNormalizedUserId())){
+ return;
+ }
+ }
if (DBG) {
Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f66cfa9..b4ca511 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -200,6 +200,10 @@
private boolean mIsAppImportanceLocked;
private ArraySet<Uri> mGrantableUris;
+ // Whether this notification record should have an update logged the next time notifications
+ // are sorted.
+ private boolean mPendingLogUpdate = false;
+
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel) {
this.sbn = sbn;
@@ -648,17 +652,23 @@
final ArrayList<String> people =
adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE);
setPeopleOverride(people);
+ EventLogTags.writeNotificationAdjusted(
+ getKey(), Adjustment.KEY_PEOPLE, people.toString());
}
if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
final ArrayList<SnoozeCriterion> snoozeCriterionList =
adjustment.getSignals().getParcelableArrayList(
Adjustment.KEY_SNOOZE_CRITERIA);
setSnoozeCriteria(snoozeCriterionList);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_SNOOZE_CRITERIA,
+ snoozeCriterionList.toString());
}
if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) {
final String groupOverrideKey =
adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
setOverrideGroupKey(groupOverrideKey);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_GROUP_KEY,
+ groupOverrideKey);
}
if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
// Only allow user sentiment update from assistant if user hasn't already
@@ -667,27 +677,42 @@
&& (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
setUserSentiment(adjustment.getSignals().getInt(
Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_USER_SENTIMENT,
+ Integer.toString(getUserSentiment()));
}
}
if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) {
setSystemGeneratedSmartActions(
signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS));
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_CONTEXTUAL_ACTIONS,
+ getSystemGeneratedSmartActions().toString());
}
if (signals.containsKey(Adjustment.KEY_TEXT_REPLIES)) {
setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES));
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_TEXT_REPLIES,
+ getSmartReplies().toString());
}
if (signals.containsKey(Adjustment.KEY_IMPORTANCE)) {
int importance = signals.getInt(Adjustment.KEY_IMPORTANCE);
importance = Math.max(IMPORTANCE_UNSPECIFIED, importance);
importance = Math.min(IMPORTANCE_HIGH, importance);
setAssistantImportance(importance);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_IMPORTANCE,
+ Integer.toString(importance));
}
if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_RANKING_SCORE,
+ Float.toString(mRankingScore));
}
if (signals.containsKey(Adjustment.KEY_NOT_CONVERSATION)) {
mIsNotConversationOverride = signals.getBoolean(
Adjustment.KEY_NOT_CONVERSATION);
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_NOT_CONVERSATION,
+ Boolean.toString(mIsNotConversationOverride));
}
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
@@ -1478,6 +1503,24 @@
return sbn;
}
+ /**
+ * Returns whether this record's ranking score is approximately equal to otherScore
+ * (the difference must be within 0.0001).
+ */
+ public boolean rankingScoreMatches(float otherScore) {
+ return Math.abs(mRankingScore - otherScore) < 0.0001;
+ }
+
+ protected void setPendingLogUpdate(boolean pendingLogUpdate) {
+ mPendingLogUpdate = pendingLogUpdate;
+ }
+
+ // If a caller of this function subsequently logs the update, they should also call
+ // setPendingLogUpdate to false to make sure other callers don't also do so.
+ protected boolean hasPendingLogUpdate() {
+ return mPendingLogUpdate;
+ }
+
@VisibleForTesting
static final class Light {
public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 0e2ff75..7d8564f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -59,6 +59,20 @@
InstanceId groupId);
/**
+ * Logs a NotificationReported atom reflecting an adjustment to a notification.
+ * Unlike maybeLogNotificationPosted, this method is guaranteed to log a notification update,
+ * so the caller must take responsibility for checking that that logging update is necessary,
+ * and that the notification is meaningfully changed.
+ * @param r The NotificationRecord. If null, no action is taken.
+ * @param position The position at which this notification is ranked.
+ * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user.
+ * @param groupId The instance Id of the group summary notification, or null.
+ */
+ void logNotificationAdjusted(@Nullable NotificationRecord r,
+ int position, int buzzBeepBlink,
+ InstanceId groupId);
+
+ /**
* Logs a notification cancel / dismiss event using UiEventReported (event ids from the
* NotificationCancelledEvents enum).
* @param r The NotificationRecord. If null, no action is taken.
@@ -96,7 +110,9 @@
@UiEvent(doc = "New notification enqueued to post")
NOTIFICATION_POSTED(162),
@UiEvent(doc = "Notification substantially updated, or alerted again.")
- NOTIFICATION_UPDATED(163);
+ NOTIFICATION_UPDATED(163),
+ @UiEvent(doc = "Notification adjusted by assistant.")
+ NOTIFICATION_ADJUSTED(908);
private final int mId;
NotificationReportedEvent(int id) {
@@ -349,7 +365,8 @@
&& Objects.equals(r.getSbn().getNotification().category,
old.getSbn().getNotification().category)
&& (r.getImportance() == old.getImportance())
- && (getLoggingImportance(r) == getLoggingImportance(old)));
+ && (getLoggingImportance(r) == getLoggingImportance(old))
+ && r.rankingScoreMatches(old.getRankingScore()));
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 1a99fb0e..1859ec4 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -16,6 +16,8 @@
package com.android.server.notification;
+import android.annotation.Nullable;
+
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
@@ -37,33 +39,49 @@
if (!p.shouldLogReported(buzzBeepBlink)) {
return;
}
+ writeNotificationReportedAtom(p, NotificationReportedEvent.fromRecordPair(p),
+ position, buzzBeepBlink, groupId);
+ }
+
+ @Override
+ public void logNotificationAdjusted(@Nullable NotificationRecord r,
+ int position, int buzzBeepBlink,
+ InstanceId groupId) {
+ NotificationRecordPair p = new NotificationRecordPair(r, null);
+ writeNotificationReportedAtom(p, NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ position, buzzBeepBlink, groupId);
+ }
+
+ private void writeNotificationReportedAtom(NotificationRecordPair p,
+ NotificationReportedEvent eventType, int position, int buzzBeepBlink,
+ InstanceId groupId) {
FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
- /* int32 event_id = 1 */ NotificationReportedEvent.fromRecordPair(p).getId(),
- /* int32 uid = 2 */ r.getUid(),
- /* string package_name = 3 */ r.getSbn().getPackageName(),
+ /* int32 event_id = 1 */ eventType.getId(),
+ /* int32 uid = 2 */ p.r.getUid(),
+ /* string package_name = 3 */ p.r.getSbn().getPackageName(),
/* int32 instance_id = 4 */ p.getInstanceId(),
/* int32 notification_id_hash = 5 */ p.getNotificationIdHash(),
/* int32 channel_id_hash = 6 */ p.getChannelIdHash(),
/* string group_id_hash = 7 */ p.getGroupIdHash(),
/* int32 group_instance_id = 8 */ (groupId == null) ? 0 : groupId.getId(),
- /* bool is_group_summary = 9 */ r.getSbn().getNotification().isGroupSummary(),
- /* string category = 10 */ r.getSbn().getNotification().category,
+ /* bool is_group_summary = 9 */ p.r.getSbn().getNotification().isGroupSummary(),
+ /* string category = 10 */ p.r.getSbn().getNotification().category,
/* int32 style = 11 */ p.getStyle(),
/* int32 num_people = 12 */ p.getNumPeople(),
/* int32 position = 13 */ position,
/* android.stats.sysui.NotificationImportance importance = 14 */
- NotificationRecordLogger.getLoggingImportance(r),
+ NotificationRecordLogger.getLoggingImportance(p.r),
/* int32 alerting = 15 */ buzzBeepBlink,
/* NotificationImportanceExplanation importance_source = 16 */
- r.getImportanceExplanationCode(),
+ p.r.getImportanceExplanationCode(),
/* android.stats.sysui.NotificationImportance importance_initial = 17 */
- r.getInitialImportance(),
+ p.r.getInitialImportance(),
/* NotificationImportanceExplanation importance_initial_source = 18 */
- r.getInitialImportanceExplanationCode(),
+ p.r.getInitialImportanceExplanationCode(),
/* android.stats.sysui.NotificationImportance importance_asst = 19 */
- r.getAssistantImportance(),
+ p.r.getAssistantImportance(),
/* int32 assistant_hash = 20 */ p.getAssistantHash(),
- /* float assistant_ranking_score = 21 */ r.getRankingScore()
+ /* float assistant_ranking_score = 21 */ p.r.getRankingScore()
);
}
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 7112ae1..628a322 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -44,6 +45,8 @@
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Slog;
@@ -392,19 +395,28 @@
+ "--context <snooze-criterion-id>) <key>");
return 1;
}
- if (null == mDirectService.getNotificationRecord(key)) {
- pw.println("error: no notification matching key: " + key);
- return 1;
- }
if (duration > 0 || criterion != null) {
+ ShellNls nls = new ShellNls();
+ nls.registerAsSystemService(mDirectService.getContext(),
+ new ComponentName(nls.getClass().getPackageName(),
+ nls.getClass().getName()),
+ ActivityManager.getCurrentUser());
+ if (!waitForBind(nls)) {
+ pw.println("error: could not bind a listener in time");
+ return 1;
+ }
if (duration > 0) {
pw.println(String.format("snoozing <%s> until time: %s", key,
new Date(System.currentTimeMillis() + duration)));
+ nls.snoozeNotification(key, duration);
} else {
pw.println(String.format("snoozing <%s> until criterion: %s", key,
criterion));
+ nls.snoozeNotification(key, criterion);
}
- mDirectService.snoozeNotificationInt(key, duration, criterion, null);
+ waitForSnooze(nls, key);
+ nls.unregisterAsSystemService();
+ waitForUnbind(nls);
} else {
pw.println("error: invalid value for --" + subflag + ": " + flagarg);
return 1;
@@ -527,14 +539,17 @@
final PendingIntent pi;
if ("broadcast".equals(intentKind)) {
pi = PendingIntent.getBroadcastAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED,
UserHandle.CURRENT);
} else if ("service".equals(intentKind)) {
pi = PendingIntent.getService(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED);
} else {
pi = PendingIntent.getActivityAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
UserHandle.CURRENT);
}
builder.setContentIntent(pi);
@@ -685,9 +700,79 @@
return 0;
}
+ private void waitForSnooze(ShellNls nls, String key) {
+ for (int i = 0; i < 20; i++) {
+ StatusBarNotification[] sbns = nls.getSnoozedNotifications();
+ for (StatusBarNotification sbn : sbns) {
+ if (sbn.getKey().equals(key)) {
+ return;
+ }
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return;
+ }
+
+ private boolean waitForBind(ShellNls nls) {
+ for (int i = 0; i < 20; i++) {
+ if (nls.isConnected) {
+ Slog.i(TAG, "Bound Shell NLS");
+ return true;
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return false;
+ }
+
+ private void waitForUnbind(ShellNls nls) {
+ for (int i = 0; i < 10; i++) {
+ if (!nls.isConnected) {
+ Slog.i(TAG, "Unbound Shell NLS");
+ return;
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
@Override
public void onHelp() {
getOutPrintWriter().println(USAGE);
}
+
+ @SuppressLint("OverrideAbstract")
+ private static class ShellNls extends NotificationListenerService {
+ private static ShellNls
+ sNotificationListenerInstance = null;
+ boolean isConnected;
+
+ @Override
+ public void onListenerConnected() {
+ super.onListenerConnected();
+ sNotificationListenerInstance = this;
+ isConnected = true;
+ }
+ @Override
+ public void onListenerDisconnected() {
+ isConnected = false;
+ }
+
+ public static ShellNls getInstance() {
+ return sNotificationListenerInstance;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 96bde3d..e8a3a81 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -330,8 +330,7 @@
}
}
- if (isShortcutOk(channel) && isDeletionOk(channel)
- && !channel.isSoundMissing()) {
+ if (isShortcutOk(channel) && isDeletionOk(channel)) {
r.channels.put(id, channel);
}
}
diff --git a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
index 947405e..b276c6f 100644
--- a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
+++ b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
@@ -19,10 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Build;
import android.os.IDeviceIdentifiersPolicyService;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.UserHandle;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.server.SystemService;
@@ -65,11 +68,31 @@
@Override
public @Nullable String getSerialForPackage(String callingPackage,
String callingFeatureId) throws RemoteException {
+ if (!checkPackageBelongsToCaller(callingPackage)) {
+ throw new IllegalArgumentException(
+ "Invalid callingPackage or callingPackage does not belong to caller's uid:"
+ + Binder.getCallingUid());
+ }
+
if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext,
callingPackage, callingFeatureId, "getSerial")) {
return Build.UNKNOWN;
}
return SystemProperties.get("ro.serialno", Build.UNKNOWN);
}
+
+ private boolean checkPackageBelongsToCaller(String callingPackage) {
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ int callingPackageUid;
+ try {
+ callingPackageUid = mContext.getPackageManager().getPackageUidAsUser(
+ callingPackage, callingUserId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ return callingPackageUid == callingUid;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8e18508..3e0a7fd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2134,6 +2134,13 @@
boolean filterAppAccess(String packageName, int callingUid, int userId);
@LiveImplementation(override = LiveImplementation.MANDATORY)
void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState);
+ @LiveImplementation(override = LiveImplementation.NOT_ALLOWED)
+ FindPreferredActivityBodyResult findPreferredActivityInternal(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered);
+ @LiveImplementation(override = LiveImplementation.NOT_ALLOWED)
+ ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean debug, int userId);
}
/**
@@ -2916,7 +2923,24 @@
}
allHomeCandidates.addAll(resolveInfos);
- final String packageName = mDefaultAppProvider.getDefaultHome(userId);
+ String packageName = mDefaultAppProvider.getDefaultHome(userId);
+ if (packageName == null) {
+ // Role changes are not and cannot be atomic because its implementation lives inside
+ // a system app, so when the home role changes, there is a window when the previous
+ // role holder is removed and the new role holder is granted the preferred activity,
+ // but hasn't become the role holder yet. However, this case may be easily hit
+ // because the preferred activity change triggers a broadcast and receivers may try
+ // to get the default home activity there. So we need to fix it for this time
+ // window, and an easy workaround is to fallback to the current preferred activity.
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+ final boolean filtered = appId >= Process.FIRST_APPLICATION_UID;
+ FindPreferredActivityBodyResult result = findPreferredActivityInternal(
+ intent, null, 0, resolveInfos, true, false, false, userId, filtered);
+ ResolveInfo preferredResolveInfo = result.mPreferredResolveInfo;
+ if (preferredResolveInfo != null && preferredResolveInfo.activityInfo != null) {
+ packageName = preferredResolveInfo.activityInfo.packageName;
+ }
+ }
if (packageName == null) {
return null;
}
@@ -4848,6 +4872,284 @@
}
} // switch
}
+
+ // The body of findPreferredActivity.
+ protected FindPreferredActivityBodyResult findPreferredActivityBody(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
+ int callingUid, boolean isDeviceProvisioned) {
+ FindPreferredActivityBodyResult result = new FindPreferredActivityBodyResult();
+
+ flags = updateFlagsForResolve(
+ flags, userId, callingUid, false /*includeInstantApps*/,
+ isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+ resolvedType, flags));
+ intent = updateIntentForResolve(intent);
+
+ // Try to find a matching persistent preferred activity.
+ result.mPreferredResolveInfo = findPersistentPreferredActivityLP(intent,
+ resolvedType, flags, query, debug, userId);
+
+ // If a persistent preferred activity matched, use it.
+ if (result.mPreferredResolveInfo != null) {
+ return result;
+ }
+
+ PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
+ // Get the list of preferred activities that handle the intent
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
+ List<PreferredActivity> prefs = pir != null
+ ? pir.queryIntent(intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+ userId)
+ : null;
+ if (prefs != null && prefs.size() > 0) {
+
+ // First figure out how good the original match set is.
+ // We will only allow preferred activities that came
+ // from the same match quality.
+ int match = 0;
+
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
+
+ final int N = query.size();
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Match for " + ri.activityInfo
+ + ": 0x" + Integer.toHexString(match));
+ }
+ if (ri.match > match) {
+ match = ri.match;
+ }
+ }
+
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Best match: 0x" + Integer.toHexString(match));
+ }
+ match &= IntentFilter.MATCH_CATEGORY_MASK;
+ final int M = prefs.size();
+ for (int i = 0; i < M; i++) {
+ final PreferredActivity pa = prefs.get(i);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Checking PreferredActivity ds="
+ + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
+ + "\n component=" + pa.mPref.mComponent);
+ pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ }
+ if (pa.mPref.mMatch != match) {
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Skipping bad match "
+ + Integer.toHexString(pa.mPref.mMatch));
+ }
+ continue;
+ }
+ // If it's not an "always" type preferred activity and that's what we're
+ // looking for, skip it.
+ if (always && !pa.mPref.mAlways) {
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
+ continue;
+ }
+ final ActivityInfo ai = getActivityInfo(
+ pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
+ | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ userId);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Found preferred activity:");
+ if (ai != null) {
+ ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ } else {
+ Slog.v(TAG, " null");
+ }
+ }
+ final boolean excludeSetupWizardHomeActivity = isHomeIntent(intent)
+ && !isDeviceProvisioned;
+ final boolean allowSetMutation = !excludeSetupWizardHomeActivity
+ && !queryMayBeFiltered;
+ if (ai == null) {
+ // Do not remove launcher's preferred activity during SetupWizard
+ // due to it may not install yet
+ if (!allowSetMutation) {
+ continue;
+ }
+
+ // This previously registered preferred activity
+ // component is no longer known. Most likely an update
+ // to the app was installed and in the new version this
+ // component no longer exists. Clean it up by removing
+ // it from the preferred activities list, and skip it.
+ Slog.w(TAG, "Removing dangling preferred activity: "
+ + pa.mPref.mComponent);
+ pir.removeFilter(pa);
+ result.mChanged = true;
+ continue;
+ }
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (!ri.activityInfo.applicationInfo.packageName
+ .equals(ai.applicationInfo.packageName)) {
+ continue;
+ }
+ if (!ri.activityInfo.name.equals(ai.name)) {
+ continue;
+ }
+
+ if (removeMatches && allowSetMutation) {
+ pir.removeFilter(pa);
+ result.mChanged = true;
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
+ }
+ break;
+ }
+
+ // Okay we found a previously set preferred or last chosen app.
+ // If the result set is different from when this
+ // was created, and is not a subset of the preferred set, we need to
+ // clear it and re-ask the user their preference, if we're looking for
+ // an "always" type entry.
+
+ if (always && !pa.mPref.sameSet(query, excludeSetupWizardHomeActivity)) {
+ if (pa.mPref.isSuperset(query, excludeSetupWizardHomeActivity)) {
+ if (allowSetMutation) {
+ // some components of the set are no longer present in
+ // the query, but the preferred activity can still be reused
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Result set changed, but PreferredActivity"
+ + " is still valid as only non-preferred"
+ + " components were removed for " + intent
+ + " type " + resolvedType);
+ }
+ // remove obsolete components and re-add the up-to-date
+ // filter
+ PreferredActivity freshPa = new PreferredActivity(pa,
+ pa.mPref.mMatch,
+ pa.mPref.discardObsoleteComponents(query),
+ pa.mPref.mComponent,
+ pa.mPref.mAlways);
+ pir.removeFilter(pa);
+ pir.addFilter(freshPa);
+ result.mChanged = true;
+ } else {
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Do not remove preferred activity");
+ }
+ }
+ } else {
+ if (allowSetMutation) {
+ Slog.i(TAG,
+ "Result set changed, dropping preferred activity "
+ + "for " + intent + " type "
+ + resolvedType);
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG,
+ "Removing preferred activity since set changed "
+ + pa.mPref.mComponent);
+ }
+ pir.removeFilter(pa);
+ // Re-add the filter as a "last chosen" entry (!always)
+ PreferredActivity lastChosen = new PreferredActivity(
+ pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
+ false);
+ pir.addFilter(lastChosen);
+ result.mChanged = true;
+ }
+ result.mPreferredResolveInfo = null;
+ return result;
+ }
+ }
+
+ // Yay! Either the set matched or we're looking for the last chosen
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Returning preferred activity: "
+ + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ }
+ result.mPreferredResolveInfo = ri;
+ return result;
+ }
+ }
+ }
+ return result;
+ }
+
+ public final FindPreferredActivityBodyResult findPreferredActivityInternal(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+
+ final int callingUid = Binder.getCallingUid();
+ // Do NOT hold the packages lock; this calls up into the settings provider which
+ // could cause a deadlock.
+ final boolean isDeviceProvisioned =
+ android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
+ // Find the preferred activity - the lock is held inside the method.
+ return findPreferredActivityBody(
+ intent, resolvedType, flags, query, always, removeMatches, debug,
+ userId, queryMayBeFiltered, callingUid, isDeviceProvisioned);
+ }
+
+ public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType,
+ int flags, List<ResolveInfo> query, boolean debug, int userId) {
+ final int N = query.size();
+ PersistentPreferredIntentResolver ppir =
+ mSettings.getPersistentPreferredActivities(userId);
+ // Get the list of persistent preferred activities that handle the intent
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Looking for persistent preferred activities...");
+ }
+ List<PersistentPreferredActivity> pprefs = ppir != null
+ ? ppir.queryIntent(intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+ userId)
+ : null;
+ if (pprefs != null && pprefs.size() > 0) {
+ final int M = pprefs.size();
+ for (int i = 0; i < M; i++) {
+ final PersistentPreferredActivity ppa = pprefs.get(i);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Checking PersistentPreferredActivity ds="
+ + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
+ + "\n component=" + ppa.mComponent);
+ ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ }
+ final ActivityInfo ai = getActivityInfo(ppa.mComponent,
+ flags | MATCH_DISABLED_COMPONENTS, userId);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Found persistent preferred activity:");
+ if (ai != null) {
+ ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ } else {
+ Slog.v(TAG, " null");
+ }
+ }
+ if (ai == null) {
+ // This previously registered persistent preferred activity
+ // component is no longer known. Ignore it and do NOT remove it.
+ continue;
+ }
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (!ri.activityInfo.applicationInfo.packageName
+ .equals(ai.applicationInfo.packageName)) {
+ continue;
+ }
+ if (!ri.activityInfo.name.equals(ai.name)) {
+ continue;
+ }
+ // Found a persistent preference that can handle the intent.
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Returning persistent preferred activity: "
+ + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ }
+ return ri;
+ }
+ }
+ }
+ return null;
+ }
}
/**
@@ -5007,6 +5309,16 @@
super.dump(type, fd, pw, dumpState);
}
}
+ public final FindPreferredActivityBodyResult findPreferredActivityBody(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
+ int callingUid, boolean isDeviceProvisioned) {
+ synchronized (mLock) {
+ return super.findPreferredActivityBody(intent, resolvedType, flags, query, always,
+ removeMatches, debug, userId, queryMayBeFiltered, callingUid,
+ isDeviceProvisioned);
+ }
+ }
}
/**
@@ -5574,6 +5886,28 @@
current.release();
}
}
+ public final FindPreferredActivityBodyResult findPreferredActivityInternal(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+ ThreadComputer current = live();
+ try {
+ return current.mComputer.findPreferredActivityInternal(intent, resolvedType, flags,
+ query, always, removeMatches, debug, userId, queryMayBeFiltered);
+ } finally {
+ current.release();
+ }
+ }
+ public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean debug,
+ int userId) {
+ ThreadComputer current = live();
+ try {
+ return current.mComputer.findPersistentPreferredActivityLP(intent, resolvedType,
+ flags, query, debug, userId);
+ } finally {
+ current.release();
+ }
+ }
}
@@ -7222,6 +7556,7 @@
t.traceEnd();
mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions);
+ mPermissionManager.readLegacyPermissionStateTEMP();
if (!mOnlyCore && mFirstBoot) {
requestCopyPreoptedFiles(mInjector);
@@ -7644,7 +7979,6 @@
+ ((SystemClock.uptimeMillis()-startTime)/1000f)
+ " seconds");
- mPermissionManager.readLegacyPermissionStateTEMP();
// If the build fingerprint has changed since the last time we booted,
// we need to re-grant app permission to catch any new ones that
// appear. This is really a hack, and means that apps can in some
@@ -9077,7 +9411,7 @@
/**
* Update given intent when being used to request {@link ResolveInfo}.
*/
- private Intent updateIntentForResolve(Intent intent) {
+ private static Intent updateIntentForResolve(Intent intent) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
}
@@ -10249,7 +10583,7 @@
userId);
// Find any earlier preferred or last chosen entries and nuke them
findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, 0, false, true, false, userId);
+ intent, resolvedType, flags, query, false, true, false, userId);
// Add the new activity as the last chosen for this filter
addPreferredActivity(filter, match, null, activity, false, userId,
"Setting last chosen", false);
@@ -10265,7 +10599,7 @@
final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
userId);
return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, 0, false, false, false, userId);
+ intent, resolvedType, flags, query, false, false, false, userId);
}
private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
@@ -10307,7 +10641,7 @@
// If we have saved a preference for a preferred activity for
// this Intent, use that.
ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
- flags, query, r0.priority, true, false, debug, userId, queryMayBeFiltered);
+ flags, query, true, false, debug, userId, queryMayBeFiltered);
if (ri != null) {
return ri;
}
@@ -10420,287 +10754,72 @@
}
@GuardedBy("mLock")
- private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType,
+ private ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType,
int flags, List<ResolveInfo> query, boolean debug, int userId) {
- final int N = query.size();
- PersistentPreferredIntentResolver ppir = mSettings.getPersistentPreferredActivities(userId);
- // Get the list of persistent preferred activities that handle the intent
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for presistent preferred activities...");
- List<PersistentPreferredActivity> pprefs = ppir != null
- ? ppir.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId)
- : null;
- if (pprefs != null && pprefs.size() > 0) {
- final int M = pprefs.size();
- for (int i=0; i<M; i++) {
- final PersistentPreferredActivity ppa = pprefs.get(i);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Checking PersistentPreferredActivity ds="
- + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
- + "\n component=" + ppa.mComponent);
- ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- }
- final ActivityInfo ai = getActivityInfo(ppa.mComponent,
- flags | MATCH_DISABLED_COMPONENTS, userId);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Found persistent preferred activity:");
- if (ai != null) {
- ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- } else {
- Slog.v(TAG, " null");
- }
- }
- if (ai == null) {
- // This previously registered persistent preferred activity
- // component is no longer known. Ignore it and do NOT remove it.
- continue;
- }
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (!ri.activityInfo.applicationInfo.packageName
- .equals(ai.applicationInfo.packageName)) {
- continue;
- }
- if (!ri.activityInfo.name.equals(ai.name)) {
- continue;
- }
- // Found a persistent preference that can handle the intent.
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Returning persistent preferred activity: " +
- ri.activityInfo.packageName + "/" + ri.activityInfo.name);
- }
- return ri;
- }
- }
- }
- return null;
+ return mComputer.findPersistentPreferredActivityLP(intent,
+ resolvedType,
+ flags, query, debug, userId);
}
- private boolean isHomeIntent(Intent intent) {
+ private static boolean isHomeIntent(Intent intent) {
return ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(CATEGORY_HOME)
&& intent.hasCategory(CATEGORY_DEFAULT);
}
+
+ // findPreferredActivityBody returns two items: a "things changed" flag and a
+ // ResolveInfo, which is the preferred activity itself.
+ private static class FindPreferredActivityBodyResult {
+ boolean mChanged;
+ ResolveInfo mPreferredResolveInfo;
+ }
+
+ private FindPreferredActivityBodyResult findPreferredActivityInternal(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+ return mComputer.findPreferredActivityInternal(
+ intent, resolvedType, flags,
+ query, always,
+ removeMatches, debug, userId, queryMayBeFiltered);
+ }
+
ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
- List<ResolveInfo> query, int priority, boolean always,
+ List<ResolveInfo> query, boolean always,
boolean removeMatches, boolean debug, int userId) {
return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, priority, always, removeMatches, debug, userId,
+ intent, resolvedType, flags, query, always, removeMatches, debug, userId,
UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
}
// TODO: handle preferred activities missing while user has amnesia
/** <b>must not hold {@link #mLock}</b> */
ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
- List<ResolveInfo> query, int priority, boolean always,
+ List<ResolveInfo> query, boolean always,
boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
if (Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ " is holding mLock", new Throwable());
}
if (!mUserManager.exists(userId)) return null;
- final int callingUid = Binder.getCallingUid();
- // Do NOT hold the packages lock; this calls up into the settings provider which
- // could cause a deadlock.
- final boolean isDeviceProvisioned =
- android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
- flags = updateFlagsForResolve(
- flags, userId, callingUid, false /*includeInstantApps*/,
- isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
- flags));
- intent = updateIntentForResolve(intent);
- // writer
- synchronized (mLock) {
- // Try to find a matching persistent preferred activity.
- ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
- debug, userId);
- // If a persistent preferred activity matched, use it.
- if (pri != null) {
- return pri;
+ FindPreferredActivityBodyResult body = findPreferredActivityInternal(
+ intent, resolvedType, flags, query, always,
+ removeMatches, debug, userId, queryMayBeFiltered);
+ if (body.mChanged) {
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
}
-
- PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
- // Get the list of preferred activities that handle the intent
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
- List<PreferredActivity> prefs = pir != null
- ? pir.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId)
- : null;
- if (prefs != null && prefs.size() > 0) {
- boolean changed = false;
- try {
- // First figure out how good the original match set is.
- // We will only allow preferred activities that came
- // from the same match quality.
- int match = 0;
-
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
-
- final int N = query.size();
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Match for " + ri.activityInfo
- + ": 0x" + Integer.toHexString(match));
- if (ri.match > match) {
- match = ri.match;
- }
- }
-
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Best match: 0x"
- + Integer.toHexString(match));
-
- match &= IntentFilter.MATCH_CATEGORY_MASK;
- final int M = prefs.size();
- for (int i=0; i<M; i++) {
- final PreferredActivity pa = prefs.get(i);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Checking PreferredActivity ds="
- + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
- + "\n component=" + pa.mPref.mComponent);
- pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- }
- if (pa.mPref.mMatch != match) {
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping bad match "
- + Integer.toHexString(pa.mPref.mMatch));
- continue;
- }
- // If it's not an "always" type preferred activity and that's what we're
- // looking for, skip it.
- if (always && !pa.mPref.mAlways) {
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
- continue;
- }
- final ActivityInfo ai = getActivityInfo(
- pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
- | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- userId);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Found preferred activity:");
- if (ai != null) {
- ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- } else {
- Slog.v(TAG, " null");
- }
- }
- final boolean excludeSetupWizardHomeActivity = isHomeIntent(intent)
- && !isDeviceProvisioned;
- final boolean allowSetMutation = !excludeSetupWizardHomeActivity
- && !queryMayBeFiltered;
- if (ai == null) {
- // Do not remove launcher's preferred activity during SetupWizard
- // due to it may not install yet
- if (!allowSetMutation) {
- continue;
- }
-
- // This previously registered preferred activity
- // component is no longer known. Most likely an update
- // to the app was installed and in the new version this
- // component no longer exists. Clean it up by removing
- // it from the preferred activities list, and skip it.
- Slog.w(TAG, "Removing dangling preferred activity: "
- + pa.mPref.mComponent);
- pir.removeFilter(pa);
- changed = true;
- continue;
- }
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (!ri.activityInfo.applicationInfo.packageName
- .equals(ai.applicationInfo.packageName)) {
- continue;
- }
- if (!ri.activityInfo.name.equals(ai.name)) {
- continue;
- }
-
- if (removeMatches && allowSetMutation) {
- pir.removeFilter(pa);
- changed = true;
- if (DEBUG_PREFERRED) {
- Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
- }
- break;
- }
-
- // Okay we found a previously set preferred or last chosen app.
- // If the result set is different from when this
- // was created, and is not a subset of the preferred set, we need to
- // clear it and re-ask the user their preference, if we're looking for
- // an "always" type entry.
-
- if (always && !pa.mPref.sameSet(query, excludeSetupWizardHomeActivity)) {
- if (pa.mPref.isSuperset(query, excludeSetupWizardHomeActivity)) {
- if (allowSetMutation) {
- // some components of the set are no longer present in
- // the query, but the preferred activity can still be reused
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Result set changed, but PreferredActivity"
- + " is still valid as only non-preferred"
- + " components were removed for " + intent
- + " type " + resolvedType);
- }
- // remove obsolete components and re-add the up-to-date
- // filter
- PreferredActivity freshPa = new PreferredActivity(pa,
- pa.mPref.mMatch,
- pa.mPref.discardObsoleteComponents(query),
- pa.mPref.mComponent,
- pa.mPref.mAlways);
- pir.removeFilter(pa);
- pir.addFilter(freshPa);
- changed = true;
- } else {
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Do not remove preferred activity");
- }
- }
- } else {
- if (allowSetMutation) {
- Slog.i(TAG,
- "Result set changed, dropping preferred activity "
- + "for " + intent + " type "
- + resolvedType);
- if (DEBUG_PREFERRED) {
- Slog.v(TAG,
- "Removing preferred activity since set changed "
- + pa.mPref.mComponent);
- }
- pir.removeFilter(pa);
- // Re-add the filter as a "last chosen" entry (!always)
- PreferredActivity lastChosen = new PreferredActivity(
- pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
- false);
- pir.addFilter(lastChosen);
- changed = true;
- }
- return null;
- }
- }
-
- // Yay! Either the set matched or we're looking for the last chosen
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Returning preferred activity: "
- + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
- return ri;
- }
- }
- } finally {
- if (changed) {
- if (DEBUG_PREFERRED) {
- Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
- }
- scheduleWritePackageRestrictionsLocked(userId);
- }
- }
+ synchronized (mLock) {
+ scheduleWritePackageRestrictionsLocked(userId);
}
}
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "No preferred activity to return");
- return null;
+ if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
+ Slog.v(TAG, "No preferred activity to return");
+ }
+ return body.mPreferredResolveInfo;
}
/*
@@ -16053,8 +16172,7 @@
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
- void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
- boolean suspended) {
+ void sendPackagesSuspendedForUser(String intent, String[] pkgList, int[] uidList, int userId) {
final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
@@ -16095,11 +16213,8 @@
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
? null : allowListsToSend.get(i);
- sendPackageBroadcast(
- suspended ? Intent.ACTION_PACKAGES_SUSPENDED
- : Intent.ACTION_PACKAGES_UNSUSPENDED,
- null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
- userIds, null, allowList, null);
+ sendPackageBroadcast(intent, null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null,
+ null, userIds, null, allowList, null);
}
}
@@ -16413,6 +16528,8 @@
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
+ final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
+ final IntArray modifiedUids = new IntArray(packageNames.length);
final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
final boolean[] canSuspend = suspended ? canSuspendPackageForUserInternal(packageNames,
userId) : null;
@@ -16440,13 +16557,14 @@
unactionedPackages.add(packageName);
continue;
}
- boolean packageUnsuspended;
+ final boolean packageUnsuspended;
+ final boolean packageModified;
synchronized (mLock) {
if (suspended) {
- pkgSetting.addOrUpdateSuspension(callingPackage, dialogInfo, appExtras,
- launcherExtras, userId);
+ packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
+ dialogInfo, appExtras, launcherExtras, userId);
} else {
- pkgSetting.removeSuspension(callingPackage, userId);
+ packageModified = pkgSetting.removeSuspension(callingPackage, userId);
}
packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
}
@@ -16454,18 +16572,29 @@
changedPackagesList.add(packageName);
changedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
}
+ if (packageModified) {
+ modifiedPackagesList.add(packageName);
+ modifiedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
+ }
}
if (!changedPackagesList.isEmpty()) {
- final String[] changedPackages = changedPackagesList.toArray(
- new String[changedPackagesList.size()]);
- sendPackagesSuspendedForUser(changedPackages, changedUids.toArray(), userId, suspended);
+ final String[] changedPackages = changedPackagesList.toArray(new String[0]);
+ sendPackagesSuspendedForUser(
+ suspended ? Intent.ACTION_PACKAGES_SUSPENDED
+ : Intent.ACTION_PACKAGES_UNSUSPENDED,
+ changedPackages, changedUids.toArray(), userId);
sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
}
}
- return unactionedPackages.toArray(new String[unactionedPackages.size()]);
+ // Send the suspension changed broadcast to ensure suspension state is not stale.
+ if (!modifiedPackagesList.isEmpty()) {
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
+ }
+ return unactionedPackages.toArray(new String[0]);
}
@Override
@@ -16594,7 +16723,8 @@
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
- sendPackagesSuspendedForUser(packageArray, unsuspendedUids.toArray(), userId, false);
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
+ packageArray, unsuspendedUids.toArray(), userId);
}
}
@@ -22538,8 +22668,9 @@
removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), userId, appId);
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+ StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(userId)) {
+ if (StorageManager.isUserKeyUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(userId)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -23479,7 +23610,7 @@
final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
- intent, null, 0, resolveInfos, 0, true, false, false, userId);
+ intent, null, 0, resolveInfos, true, false, false, userId);
final String packageName = preferredResolveInfo != null
&& preferredResolveInfo.activityInfo != null
? preferredResolveInfo.activityInfo.packageName : null;
@@ -25404,9 +25535,11 @@
// Reconcile app data for all started/unlocked users
final StorageManager sm = mInjector.getSystemService(StorageManager.class);
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+ StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
for (UserInfo user : mUserManager.getUsers(false /* includeDying */)) {
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)
+ && smInternal.isCeStoragePrepared(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -25746,7 +25879,8 @@
StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)
+ && smInternal.isCeStoragePrepared(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE;
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 88dd033..5536fc5 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -455,7 +455,7 @@
return state.suspendParams != null && state.suspendParams.containsKey(suspendingPackage);
}
- void addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
+ boolean addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
final PackageUserState existingUserState = modifyUserState(userId);
final PackageUserState.SuspendParams newSuspendParams =
@@ -464,21 +464,27 @@
if (existingUserState.suspendParams == null) {
existingUserState.suspendParams = new ArrayMap<>();
}
- existingUserState.suspendParams.put(suspendingPackage, newSuspendParams);
+ final PackageUserState.SuspendParams oldSuspendParams =
+ existingUserState.suspendParams.put(suspendingPackage, newSuspendParams);
existingUserState.suspended = true;
onChanged();
+ return !Objects.equals(oldSuspendParams, newSuspendParams);
}
- void removeSuspension(String suspendingPackage, int userId) {
+ boolean removeSuspension(String suspendingPackage, int userId) {
+ boolean wasModified = false;
final PackageUserState existingUserState = modifyUserState(userId);
if (existingUserState.suspendParams != null) {
- existingUserState.suspendParams.remove(suspendingPackage);
+ if (existingUserState.suspendParams.remove(suspendingPackage) != null) {
+ wasModified = true;
+ }
if (existingUserState.suspendParams.size() == 0) {
existingUserState.suspendParams = null;
}
}
existingUserState.suspended = (existingUserState.suspendParams != null);
onChanged();
+ return wasModified;
}
void removeSuspension(Predicate<String> suspendingPackagePredicate, int userId) {
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 4a913e4e..cecbeed 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -124,16 +124,10 @@
}
// Vendor mac permissions.
- // The filename has been renamed from nonplat_mac_permissions to
- // vendor_mac_permissions. Either of them should exist.
final File vendorMacPermission = new File(
Environment.getVendorDirectory(), "/etc/selinux/vendor_mac_permissions.xml");
if (vendorMacPermission.exists()) {
sMacPermissions.add(vendorMacPermission);
- } else {
- // For backward compatibility.
- sMacPermissions.add(new File(Environment.getVendorDirectory(),
- "/etc/selinux/nonplat_mac_permissions.xml"));
}
// ODM mac permissions (optional).
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fcbf40e..62d6717 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1664,6 +1664,19 @@
mContext.enforceCallingPermission(permission, message);
}
+ private void verifyCallerUserId(@UserIdInt int userId) {
+ if (isCallerSystem()) {
+ return; // no check
+ }
+
+ final int callingUid = injectBinderCallingUid();
+
+ // Otherwise, make sure the arguments are valid.
+ if (UserHandle.getUserId(callingUid) != userId) {
+ throw new SecurityException("Invalid user-ID");
+ }
+ }
+
private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
Preconditions.checkStringNotEmpty(packageName, "packageName");
@@ -2847,6 +2860,8 @@
@Override
public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
+ verifyCallerUserId(callingUserId);
+
final long token = injectClearCallingIdentity();
try {
return mShortcutRequestPinProcessor
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 08a7215..97c168d 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -1255,9 +1255,7 @@
info.diskImagePath = ai.modulePath;
info.versionCode = ai.versionCode;
info.versionName = ai.versionName;
- info.hasBootClassPathJars = ai.hasBootClassPathJars;
- info.hasDex2OatBootClassPathJars = ai.hasDex2OatBootClassPathJars;
- info.hasSystemServerClassPathJars = ai.hasSystemServerClassPathJars;
+ info.hasClassPathJars = ai.hasClassPathJars;
return info;
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d4feb3a..6d8137e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -80,6 +80,7 @@
import android.os.UserManager.EnforcingUser;
import android.os.UserManager.QuietModeFlag;
import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
import android.provider.Settings;
import android.security.GateKeeper;
import android.service.gatekeeper.IGateKeeperService;
@@ -4815,6 +4816,10 @@
// Migrate only if build fingerprints mismatch
boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
+
+ StorageManagerInternal smInternal = LocalServices.getService(StorageManagerInternal.class);
+ smInternal.markCeStoragePrepared(userId);
+
mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE, migrateAppsData);
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 7bea018..186b2b5 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -435,7 +435,8 @@
|| !pm.isGranted(Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
pkg, UserHandle.of(userId))
|| !pm.isGranted(Manifest.permission.READ_PHONE_STATE, pkg,
- UserHandle.of(userId))) {
+ UserHandle.of(userId))
+ || pm.isSysComponentOrPersistentPlatformSignedPrivApp(pkg)) {
continue;
}
@@ -558,11 +559,14 @@
grantPermissionsToSystemPackage(pm, verifier, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS);
// SetupWizard
- grantPermissionsToSystemPackage(pm,
- ArrayUtils.firstOrNull(getKnownPackages(
- PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId)), userId,
- PHONE_PERMISSIONS, CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
- CAMERA_PERMISSIONS);
+ final String setupWizardPackage = ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId));
+ grantPermissionsToSystemPackage(pm, setupWizardPackage, userId, PHONE_PERMISSIONS,
+ CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, CAMERA_PERMISSIONS);
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) {
+ grantPermissionsToSystemPackage(
+ pm, setupWizardPackage, userId, NEARBY_DEVICES_PERMISSIONS);
+ }
// Camera
grantPermissionsToSystemPackage(pm,
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
index b1676d0..ea554d3 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
@@ -30,6 +30,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.ILegacyPermissionManager;
+import android.util.EventLog;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -187,10 +188,25 @@
private void verifyCallerCanCheckAccess(String packageName, String message, int pid, int uid) {
// If the check is being requested by an app then only allow the app to query its own
// access status.
+ boolean reportError = false;
int callingUid = mInjector.getCallingUid();
int callingPid = mInjector.getCallingPid();
if (UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID && (callingUid != uid
|| callingPid != pid)) {
+ reportError = true;
+ }
+ // If the query is against an app on the device, then the check should only be allowed if
+ // the provided uid matches that of the specified package.
+ if (packageName != null && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+ int packageUid = mInjector.getPackageUidForUser(packageName, UserHandle.getUserId(uid));
+ if (uid != packageUid) {
+ EventLog.writeEvent(0x534e4554, "193441322",
+ UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID
+ ? callingUid : uid, "Package uid mismatch");
+ reportError = true;
+ }
+ }
+ if (reportError) {
String response = String.format(
"Calling uid %d, pid %d cannot access for package %s (uid=%d, pid=%d): %s",
callingUid, callingPid, packageName, uid, pid, message);
@@ -385,12 +401,14 @@
@VisibleForTesting
public static class Injector {
private final Context mContext;
+ private final PackageManagerInternal mPackageManagerInternal;
/**
* Public constructor that accepts a {@code context} within which to operate.
*/
public Injector(@NonNull Context context) {
mContext = context;
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
/**
@@ -453,5 +471,12 @@
return mContext.getPackageManager().getApplicationInfoAsUser(packageName, 0,
UserHandle.getUserHandleForUid(uid));
}
+
+ /**
+ * Returns the uid for the specified {@code packageName} under the provided {@code userId}.
+ */
+ public int getPackageUidForUser(String packageName, int userId) {
+ return mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index f733a2e..41c716c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1215,6 +1215,7 @@
private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(
@NonNull String permName) {
+ final String permissionPackageName;
final boolean isImmutablyRestrictedPermission;
synchronized (mLock) {
final Permission bp = mRegistry.getPermission(permName);
@@ -1222,15 +1223,25 @@
Slog.w(TAG, "No such permissions: " + permName);
return false;
}
+ permissionPackageName = bp.getPackageName();
isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted()
&& bp.isImmutablyRestricted();
}
+
+ final int callingUid = getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) {
+ EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName);
+ return false;
+ }
+
if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission(
Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Cannot modify allowlisting of an immutably "
+ "restricted permission: " + permName);
}
+
return true;
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/OWNERS b/services/core/java/com/android/server/pm/verify/domain/OWNERS
index c669112..445a833 100644
--- a/services/core/java/com/android/server/pm/verify/domain/OWNERS
+++ b/services/core/java/com/android/server/pm/verify/domain/OWNERS
@@ -2,4 +2,3 @@
chiuwinson@google.com
patb@google.com
-toddke@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4a97720..e05f7ef 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -484,6 +484,7 @@
int mLidNavigationAccessibility;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
+ long mLongPressOnPowerAssistantTimeoutMs;
int mVeryLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
@@ -732,6 +733,9 @@
Settings.Global.POWER_BUTTON_LONG_PRESS), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.POWER_BUTTON_VERY_LONG_PRESS), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
@@ -1732,6 +1736,8 @@
com.android.internal.R.integer.config_shortPressOnPowerBehavior);
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
+ mLongPressOnPowerAssistantTimeoutMs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnPowerDurationMs);
mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
@@ -1955,7 +1961,7 @@
*/
private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
PowerKeyRule(int gestures) {
- super(KEYCODE_POWER, gestures);
+ super(mContext, KEYCODE_POWER, gestures);
}
@Override
@@ -1970,6 +1976,15 @@
}
@Override
+ long getLongPressTimeoutMs() {
+ if (getResolvedLongPressOnPowerBehavior() == LONG_PRESS_POWER_ASSISTANT) {
+ return mLongPressOnPowerAssistantTimeoutMs;
+ } else {
+ return super.getLongPressTimeoutMs();
+ }
+ }
+
+ @Override
void onLongPress(long eventTime) {
if (mSingleKeyGestureDetector.beganFromNonInteractive()
&& !mSupportLongPressPowerWhenNonInteractive) {
@@ -1997,7 +2012,7 @@
*/
private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
BackKeyRule(int gestures) {
- super(KEYCODE_BACK, gestures);
+ super(mContext, KEYCODE_BACK, gestures);
}
@Override
@@ -2017,7 +2032,7 @@
}
private void initSingleKeyGestureRules() {
- mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
+ mSingleKeyGestureDetector = new SingleKeyGestureDetector();
int powerKeyGestures = 0;
if (hasVeryLongPressOnPowerBehavior()) {
@@ -2115,6 +2130,11 @@
Settings.Global.POWER_BUTTON_LONG_PRESS,
mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior));
+ mLongPressOnPowerAssistantTimeoutMs = Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnPowerDurationMs));
mVeryLongPressOnPowerBehavior = Settings.Global.getInt(resolver,
Settings.Global.POWER_BUTTON_VERY_LONG_PRESS,
mContext.getResources().getInteger(
@@ -5367,6 +5387,9 @@
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mLongPressOnPowerAssistantTimeoutMs=");
+ pw.println(mLongPressOnPowerAssistantTimeoutMs);
+ pw.print(prefix);
pw.print("mVeryLongPressOnPowerBehavior=");
pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
pw.print(prefix);
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 1ef2bf9..6fee69b 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -44,9 +44,6 @@
private static final int MSG_KEY_VERY_LONG_PRESS = 1;
private static final int MSG_KEY_DELAYED_PRESS = 2;
- private final long mLongPressTimeout;
- private final long mVeryLongPressTimeout;
-
private volatile int mKeyPressCounter;
private boolean mBeganFromNonInteractive = false;
@@ -86,12 +83,19 @@
* </pre>
*/
abstract static class SingleKeyRule {
+
private final int mKeyCode;
private final int mSupportedGestures;
+ private final long mDefaultLongPressTimeout;
+ private final long mDefaultVeryLongPressTimeout;
- SingleKeyRule(int keyCode, @KeyGestureFlag int supportedGestures) {
+ SingleKeyRule(Context context, int keyCode, @KeyGestureFlag int supportedGestures) {
mKeyCode = keyCode;
mSupportedGestures = supportedGestures;
+ mDefaultLongPressTimeout =
+ ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
+ mDefaultVeryLongPressTimeout = context.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout);
}
/**
@@ -134,10 +138,28 @@
*/
void onMultiPress(long downTime, int count) {}
/**
+ * Returns the timeout in milliseconds for a long press.
+ *
+ * If multipress is also supported, this should always be greater than the multipress
+ * timeout. If very long press is supported, this should always be less than the very long
+ * press timeout.
+ */
+ long getLongPressTimeoutMs() {
+ return mDefaultLongPressTimeout;
+ }
+ /**
* Callback when long press has been detected.
*/
void onLongPress(long eventTime) {}
/**
+ * Returns the timeout in milliseconds for a very long press.
+ *
+ * If long press is supported, this should always be longer than the long press timeout.
+ */
+ long getVeryLongPressTimeoutMs() {
+ return mDefaultVeryLongPressTimeout;
+ }
+ /**
* Callback when very long press has been detected.
*/
void onVeryLongPress(long eventTime) {}
@@ -151,10 +173,7 @@
}
}
- public SingleKeyGestureDetector(Context context) {
- mLongPressTimeout = ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
- mVeryLongPressTimeout = context.getResources().getInteger(
- com.android.internal.R.integer.config_veryLongPressTimeout);
+ public SingleKeyGestureDetector() {
mHandler = new KeyHandler();
}
@@ -225,14 +244,14 @@
final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
eventTime);
msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, mLongPressTimeout);
+ mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs());
}
if (mActiveRule.supportVeryLongPress()) {
final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
eventTime);
msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout);
+ mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs());
}
} else {
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 7555a7f..c91d8de 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -96,6 +96,7 @@
private static final int MSG_BROADCAST_ENHANCED_PREDICTION = 4;
private static final int MSG_PROFILE_TIMED_OUT = 5;
private static final int MSG_WIRED_CHARGING_STARTED = 6;
+ private static final int MSG_SCREEN_POLICY = 7;
private static final long[] CHARGING_VIBRATION_TIME = {
40, 40, 40, 40, 40, 40, 40, 40, 40, // ramp-up sampling rate = 40ms
@@ -120,6 +121,7 @@
private final SuspendBlocker mSuspendBlocker;
private final WindowManagerPolicy mPolicy;
private final FaceDownDetector mFaceDownDetector;
+ private final ScreenUndimDetector mScreenUndimDetector;
private final ActivityManagerInternal mActivityManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final InputMethodManagerInternal mInputMethodManagerInternal;
@@ -167,13 +169,14 @@
public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
mContext = context;
mBatteryStats = batteryStats;
mAppOps = mContext.getSystemService(AppOpsManager.class);
mSuspendBlocker = suspendBlocker;
mPolicy = policy;
mFaceDownDetector = faceDownDetector;
+ mScreenUndimDetector = screenUndimDetector;
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
@@ -620,6 +623,22 @@
}
/**
+ * Called when the screen policy changes.
+ */
+ public void onScreenPolicyUpdate(int newPolicy) {
+ if (DEBUG) {
+ Slog.d(TAG, "onScreenPolicyUpdate: newPolicy=" + newPolicy);
+ }
+
+ synchronized (mLock) {
+ Message msg = mHandler.obtainMessage(MSG_SCREEN_POLICY);
+ msg.arg1 = newPolicy;
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /**
* Dumps data for bugreports.
*
* @param pw The stream to print to.
@@ -659,6 +678,7 @@
tm.notifyUserActivity();
mPolicy.userActivity();
mFaceDownDetector.userActivity(event);
+ mScreenUndimDetector.userActivity();
}
void postEnhancedDischargePredictionBroadcast(long delayMs) {
@@ -812,6 +832,10 @@
mSuspendBlocker.release();
}
+ private void screenPolicyChanging(int screenPolicy) {
+ mScreenUndimDetector.recordScreenPolicy(screenPolicy);
+ }
+
private void lockProfile(@UserIdInt int userId) {
mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
}
@@ -852,6 +876,9 @@
case MSG_WIRED_CHARGING_STARTED:
showWiredChargingStarted(msg.arg1);
break;
+ case MSG_SCREEN_POLICY:
+ screenPolicyChanging(msg.arg1);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d982336..e7e0425 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -129,6 +129,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Objects;
/**
@@ -273,6 +274,7 @@
private final BatterySavingStats mBatterySavingStats;
private final AttentionDetector mAttentionDetector;
private final FaceDownDetector mFaceDownDetector;
+ private final ScreenUndimDetector mScreenUndimDetector;
private final BinderService mBinderService;
private final LocalService mLocalService;
private final NativeWrapper mNativeWrapper;
@@ -836,9 +838,10 @@
static class Injector {
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return new Notifier(
- looper, context, batteryStats, suspendBlocker, policy, faceDownDetector);
+ looper, context, batteryStats, suspendBlocker, policy, faceDownDetector,
+ screenUndimDetector);
}
SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
@@ -959,6 +962,7 @@
mInjector.createAmbientDisplaySuppressionController(context);
mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
mFaceDownDetector = new FaceDownDetector(this::onFlip);
+ mScreenUndimDetector = new ScreenUndimDetector();
mBatterySavingStats = new BatterySavingStats(mLock);
mBatterySaverPolicy =
@@ -1145,7 +1149,7 @@
mBatteryStats = BatteryStatsService.getService();
mNotifier = mInjector.createNotifier(Looper.getMainLooper(), mContext, mBatteryStats,
mInjector.createSuspendBlocker(this, "PowerManagerService.Broadcasts"),
- mPolicy, mFaceDownDetector);
+ mPolicy, mFaceDownDetector, mScreenUndimDetector);
mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
mInjector.createSuspendBlocker(
@@ -1180,6 +1184,7 @@
mBatterySaverController.systemReady();
mBatterySaverPolicy.systemReady();
mFaceDownDetector.systemReady(mContext);
+ mScreenUndimDetector.systemReady(mContext);
// Register for settings changes.
resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -1495,7 +1500,11 @@
mRequestWaitForNegativeProximity = true;
}
- wakeLock.mLock.unlinkToDeath(wakeLock, 0);
+ try {
+ wakeLock.mLock.unlinkToDeath(wakeLock, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink wakelock", e);
+ }
removeWakeLockLocked(wakeLock, index);
}
}
@@ -3185,6 +3194,7 @@
final boolean ready = mDisplayManagerInternal.requestPowerState(groupId,
displayPowerRequest, mRequestWaitForNegativeProximity);
+ mNotifier.onScreenPolicyUpdate(displayPowerRequest.policy);
if (DEBUG_SPEW) {
Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready
diff --git a/services/core/java/com/android/server/power/ScreenUndimDetector.java b/services/core/java/com/android/server/power/ScreenUndimDetector.java
new file mode 100644
index 0000000..951bc1f
--- /dev/null
+++ b/services/core/java/com/android/server/power/ScreenUndimDetector.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Detects when user manually undims the screen (x times) and acquires a wakelock to keep the screen
+ * on temporarily (without changing the screen timeout setting).
+ */
+public class ScreenUndimDetector {
+ private static final String TAG = "ScreenUndimDetector";
+ private static final boolean DEBUG = false;
+
+ private static final String UNDIM_DETECTOR_WAKE_LOCK = "UndimDetectorWakeLock";
+
+ /** DeviceConfig flag: is keep screen on feature enabled. */
+ static final String KEY_KEEP_SCREEN_ON_ENABLED = "keep_screen_on_enabled";
+ private static final boolean DEFAULT_KEEP_SCREEN_ON_ENABLED = true;
+ private static final int OUTCOME_POWER_BUTTON =
+ FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__POWER_BUTTON;
+ private static final int OUTCOME_TIMEOUT =
+ FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__TIMEOUT;
+ private boolean mKeepScreenOnEnabled;
+
+ /** DeviceConfig flag: how long should we keep the screen on. */
+ @VisibleForTesting
+ static final String KEY_KEEP_SCREEN_ON_FOR_MILLIS = "keep_screen_on_for_millis";
+ @VisibleForTesting
+ static final long DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS = TimeUnit.MINUTES.toMillis(10);
+ private long mKeepScreenOnForMillis;
+
+ /** DeviceConfig flag: how many user undims required to trigger keeping the screen on. */
+ @VisibleForTesting
+ static final String KEY_UNDIMS_REQUIRED = "undims_required";
+ @VisibleForTesting
+ static final int DEFAULT_UNDIMS_REQUIRED = 2;
+ private int mUndimsRequired;
+
+ /**
+ * DeviceConfig flag: what is the maximum duration between undims to still consider them
+ * consecutive.
+ */
+ @VisibleForTesting
+ static final String KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS =
+ "max_duration_between_undims_millis";
+ @VisibleForTesting
+ static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = TimeUnit.MINUTES.toMillis(5);
+ private long mMaxDurationBetweenUndimsMillis;
+
+ @VisibleForTesting
+ PowerManager.WakeLock mWakeLock;
+
+ @VisibleForTesting
+ int mCurrentScreenPolicy;
+ @VisibleForTesting
+ int mUndimCounter = 0;
+ @VisibleForTesting
+ long mUndimCounterStartedMillis;
+ private long mUndimOccurredTime = -1;
+ private long mInteractionAfterUndimTime = -1;
+ private InternalClock mClock;
+
+ public ScreenUndimDetector() {
+ mClock = new InternalClock();
+ }
+
+ ScreenUndimDetector(InternalClock clock) {
+ mClock = clock;
+ }
+
+ static class InternalClock {
+ public long getCurrentTime() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ /** Should be called in parent's systemReady() */
+ public void systemReady(Context context) {
+ readValuesFromDeviceConfig();
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ context.getMainExecutor(),
+ (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+ final PowerManager powerManager = context.getSystemService(PowerManager.class);
+ mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+ | PowerManager.ON_AFTER_RELEASE,
+ UNDIM_DETECTOR_WAKE_LOCK);
+ }
+
+ /**
+ * Launches a message that figures out the screen transitions and detects user undims. Must be
+ * called by the parent that is trying to update the screen policy.
+ */
+ public void recordScreenPolicy(int newPolicy) {
+ if (newPolicy == mCurrentScreenPolicy) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Screen policy transition: " + mCurrentScreenPolicy + " -> " + newPolicy);
+ }
+
+ // update the current policy with the new one immediately so we don't accidentally get
+ // into a loop (which is possible if the switch below triggers a new policy).
+ final int currentPolicy = mCurrentScreenPolicy;
+ mCurrentScreenPolicy = newPolicy;
+
+ if (!mKeepScreenOnEnabled) {
+ return;
+ }
+
+ switch (currentPolicy) {
+ case POLICY_DIM:
+ if (newPolicy == POLICY_BRIGHT) {
+ final long now = mClock.getCurrentTime();
+ final long timeElapsedSinceFirstUndim = now - mUndimCounterStartedMillis;
+ if (timeElapsedSinceFirstUndim >= mMaxDurationBetweenUndimsMillis) {
+ reset();
+ }
+ if (mUndimCounter == 0) {
+ mUndimCounterStartedMillis = now;
+ }
+
+ mUndimCounter++;
+
+ if (DEBUG) {
+ Slog.d(TAG, "User undim, counter=" + mUndimCounter
+ + " (required=" + mUndimsRequired + ")"
+ + ", timeElapsedSinceFirstUndim=" + timeElapsedSinceFirstUndim
+ + " (max=" + mMaxDurationBetweenUndimsMillis + ")");
+ }
+ if (mUndimCounter >= mUndimsRequired) {
+ reset();
+ if (DEBUG) {
+ Slog.d(TAG, "Acquiring a wake lock for " + mKeepScreenOnForMillis);
+ }
+ if (mWakeLock != null) {
+ mUndimOccurredTime = mClock.getCurrentTime();
+ mWakeLock.acquire(mKeepScreenOnForMillis);
+ }
+ }
+ } else {
+ if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
+ checkAndLogUndim(OUTCOME_TIMEOUT);
+ }
+ reset();
+ }
+ break;
+ case POLICY_BRIGHT:
+ if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
+ checkAndLogUndim(OUTCOME_POWER_BUTTON);
+ }
+ if (newPolicy != POLICY_DIM) {
+ reset();
+ }
+ break;
+ }
+ }
+
+ @VisibleForTesting
+ void reset() {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting the undim detector");
+ }
+ mUndimCounter = 0;
+ mUndimCounterStartedMillis = 0;
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
+ private boolean readKeepScreenOnNotificationEnabled() {
+ return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_ENABLED,
+ DEFAULT_KEEP_SCREEN_ON_ENABLED);
+ }
+
+ private long readKeepScreenOnForMillis() {
+ return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_FOR_MILLIS,
+ DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS);
+ }
+
+ private int readUndimsRequired() {
+ int undimsRequired = DeviceConfig.getInt(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ DEFAULT_UNDIMS_REQUIRED);
+
+ if (undimsRequired < 1 || undimsRequired > 5) {
+ Slog.e(TAG, "Provided undimsRequired=" + undimsRequired
+ + " is not allowed [1, 5]; using the default=" + DEFAULT_UNDIMS_REQUIRED);
+ return DEFAULT_UNDIMS_REQUIRED;
+ }
+
+ return undimsRequired;
+ }
+
+ private long readMaxDurationBetweenUndimsMillis() {
+ return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
+ DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS);
+ }
+
+ private void onDeviceConfigChange(@NonNull Set<String> keys) {
+ for (String key : keys) {
+ Slog.i(TAG, "onDeviceConfigChange; key=" + key);
+ switch (key) {
+ case KEY_KEEP_SCREEN_ON_ENABLED:
+ case KEY_KEEP_SCREEN_ON_FOR_MILLIS:
+ case KEY_UNDIMS_REQUIRED:
+ case KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS:
+ readValuesFromDeviceConfig();
+ return;
+ default:
+ Slog.i(TAG, "Ignoring change on " + key);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void readValuesFromDeviceConfig() {
+ mKeepScreenOnEnabled = readKeepScreenOnNotificationEnabled();
+ mKeepScreenOnForMillis = readKeepScreenOnForMillis();
+ mUndimsRequired = readUndimsRequired();
+ mMaxDurationBetweenUndimsMillis = readMaxDurationBetweenUndimsMillis();
+
+ Slog.i(TAG, "readValuesFromDeviceConfig():"
+ + "\nmKeepScreenOnForMillis=" + mKeepScreenOnForMillis
+ + "\nmKeepScreenOnNotificationEnabled=" + mKeepScreenOnEnabled
+ + "\nmUndimsRequired=" + mUndimsRequired);
+
+ }
+
+ /**
+ * The user interacted with the screen after an undim, indicating the phone is in use.
+ * We use this event for logging.
+ */
+ public void userActivity() {
+ if (mUndimOccurredTime != 1 && mInteractionAfterUndimTime == -1) {
+ mInteractionAfterUndimTime = mClock.getCurrentTime();
+ }
+ }
+
+ /**
+ * Checks and logs if an undim occurred.
+ *
+ * A log will occur if an undim seems to have resulted in a timeout or a direct screen off such
+ * as from a power button. Some outcomes may not be correctly assigned to a
+ * TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME value.
+ */
+ private void checkAndLogUndim(int outcome) {
+ if (mUndimOccurredTime != -1) {
+ long now = mClock.getCurrentTime();
+ FrameworkStatsLog.write(FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED,
+ outcome,
+ /* time_to_outcome_millis=*/ now - mUndimOccurredTime,
+ /* time_to_first_interaction_millis= */
+ mInteractionAfterUndimTime != -1 ? now - mInteractionAfterUndimTime : -1
+ );
+ mUndimOccurredTime = -1;
+ mInteractionAfterUndimTime = -1;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 2a95416..06253a0 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -124,12 +124,8 @@
@Override
public void read(InputStream in) throws IOException {
while (in.available() > 0) {
- try {
- DataElement dataElement = new DataElement(in);
- mCallback.onReadDataElement(dataElement.getData());
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read from storage. " + e.getMessage());
- }
+ DataElement dataElement = new DataElement(in);
+ mCallback.onReadDataElement(dataElement.getData());
}
}
}
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 174ad3a..2450dbb 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -8,4 +8,3 @@
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
-yro@google.com
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3a7e13b..abe81e1 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -783,13 +783,14 @@
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed,
- requireConfirmation, userId, opPackageName, operationId, multiSensorConfig);
+ requireConfirmation, userId, operationId, opPackageName, requestId,
+ multiSensorConfig);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 2f54f30..cb3d00a 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -89,6 +89,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
@@ -1173,6 +1174,91 @@
}
@Override
+ public List<String> getAvailableExtensionInterfaceNames(String inputId, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+ userId, "getAvailableExtensionInterfaceNames");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ITvInputService service = null;
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvInputState inputState = userState.inputMap.get(inputId);
+ if (inputState != null) {
+ ServiceState serviceState =
+ userState.serviceStateMap.get(inputState.info.getComponent());
+ if (serviceState != null && serviceState.isHardware
+ && serviceState.service != null) {
+ service = serviceState.service;
+ }
+ }
+ }
+ try {
+ if (service != null) {
+ List<String> interfaces = new ArrayList<>();
+ for (final String name : CollectionUtils.emptyIfNull(
+ service.getAvailableExtensionInterfaceNames())) {
+ String permission = service.getExtensionInterfacePermission(name);
+ if (permission == null
+ || mContext.checkPermission(permission, callingPid, callingUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ interfaces.add(name);
+ }
+ }
+ return interfaces;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in getAvailableExtensionInterfaceNames "
+ + "or getExtensionInterfacePermission", e);
+ }
+ return new ArrayList<>();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public IBinder getExtensionInterface(String inputId, String name, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+ userId, "getExtensionInterface");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ITvInputService service = null;
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvInputState inputState = userState.inputMap.get(inputId);
+ if (inputState != null) {
+ ServiceState serviceState =
+ userState.serviceStateMap.get(inputState.info.getComponent());
+ if (serviceState != null && serviceState.isHardware
+ && serviceState.service != null) {
+ service = serviceState.service;
+ }
+ }
+ }
+ try {
+ if (service != null) {
+ String permission = service.getExtensionInterfacePermission(name);
+ if (permission == null
+ || mContext.checkPermission(permission, callingPid, callingUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return service.getExtensionInterface(name);
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in getExtensionInterfacePermission "
+ + "or getExtensionInterface", e);
+ }
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
if (mContext.checkCallingPermission(
android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS)
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7713320..a51ed09 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.wallpaper;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -207,7 +208,7 @@
* wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
* every time the wallpaper is changed.
*/
- private class WallpaperObserver extends FileObserver {
+ class WallpaperObserver extends FileObserver {
final int mUserId;
final WallpaperData mWallpaper;
@@ -225,7 +226,7 @@
mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
}
- private WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+ WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
WallpaperData wallpaper = null;
synchronized (mLock) {
if (lockChanged) {
@@ -308,9 +309,18 @@
}
wallpaper.imageWallpaperPending = false;
if (sysWallpaperChanged) {
+ IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "publish system wallpaper changed!");
+ }
+ notifyWallpaperChanged(wallpaper);
+ }
+ };
// If this was the system wallpaper, rebind...
bindWallpaperComponentLocked(mImageWallpaper, true,
- false, wallpaper, null);
+ false, wallpaper, callback);
notifyColorsWhich |= FLAG_SYSTEM;
}
if (lockWallpaperChanged
@@ -330,15 +340,9 @@
}
saveSettingsLocked(wallpaper.userId);
-
- // Publish completion *after* we've persisted the changes
- if (wallpaper.setComplete != null) {
- try {
- wallpaper.setComplete.onWallpaperChanged();
- } catch (RemoteException e) {
- // if this fails we don't really care; the setting app may just
- // have crashed and that sort of thing is a fact of life.
- }
+ // Notify the client immediately if only lockscreen wallpaper changed.
+ if (lockWallpaperChanged && !sysWallpaperChanged) {
+ notifyWallpaperChanged(wallpaper);
}
}
}
@@ -352,6 +356,18 @@
}
}
+ private void notifyWallpaperChanged(WallpaperData wallpaper) {
+ // Publish completion *after* we've persisted the changes
+ if (wallpaper.setComplete != null) {
+ try {
+ wallpaper.setComplete.onWallpaperChanged();
+ } catch (RemoteException e) {
+ // if this fails we don't really care; the setting app may just
+ // have crashed and that sort of thing is a fact of life.
+ }
+ }
+ }
+
private void notifyLockWallpaperChanged() {
final IWallpaperManagerCallback cb = mKeyguardListener;
if (cb != null) {
@@ -363,7 +379,7 @@
}
}
- private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
+ void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
if (wallpaper.connection != null) {
wallpaper.connection.forEachDisplayConnector(connector -> {
notifyWallpaperColorsChangedOnDisplay(wallpaper, which, connector.mDisplayId);
@@ -567,7 +583,7 @@
* Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
* for display.
*/
- private void generateCrop(WallpaperData wallpaper) {
+ void generateCrop(WallpaperData wallpaper) {
boolean success = false;
// Only generate crop for default display.
@@ -2045,7 +2061,21 @@
}
}
+ private boolean hasCrossUserPermission() {
+ final int interactPermission =
+ mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL);
+ return interactPermission == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
public boolean hasNamedWallpaper(String name) {
+ final int callingUser = UserHandle.getCallingUserId();
+ final boolean allowCrossUser = hasCrossUserPermission();
+ if (DEBUG) {
+ Slog.d(TAG, "hasNamedWallpaper() caller " + Binder.getCallingUid()
+ + " cross-user?: " + allowCrossUser);
+ }
+
synchronized (mLock) {
List<UserInfo> users;
final long ident = Binder.clearCallingIdentity();
@@ -2055,6 +2085,11 @@
Binder.restoreCallingIdentity(ident);
}
for (UserInfo user: users) {
+ if (!allowCrossUser && callingUser != user.id) {
+ // No cross-user information for callers without permission
+ continue;
+ }
+
// ignore managed profiles
if (user.isManagedProfile()) {
continue;
@@ -2814,7 +2849,7 @@
return false;
}
- private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
+ boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
if (DEBUG_LIVE) {
Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
@@ -3101,7 +3136,7 @@
return new JournaledFile(new File(base), new File(base + ".tmp"));
}
- private void saveSettingsLocked(int userId) {
+ void saveSettingsLocked(int userId) {
JournaledFile journal = makeJournaledFile(userId);
FileOutputStream fstream = null;
try {
@@ -3250,7 +3285,7 @@
* Important: this method loads settings to initialize the given user's wallpaper data if
* there is no current in-memory state.
*/
- private WallpaperData getWallpaperSafeLocked(int userId, int which) {
+ WallpaperData getWallpaperSafeLocked(int userId, int which) {
// We're setting either just system (work with the system wallpaper),
// both (also work with the system wallpaper), or just the lock
// wallpaper (update against the existing lock wallpaper if any).
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3a4faf7..e02e867 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -21,6 +21,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -53,6 +55,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
@@ -64,6 +67,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.service.voice.VoiceInteractionManagerInternal;
import android.util.Slog;
import android.view.RemoteAnimationDefinition;
@@ -74,6 +78,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.uri.NeededUriGrants;
import com.android.server.vr.VrManagerInternal;
@@ -557,20 +562,45 @@
@Override
public int getLaunchedFromUid(IBinder token) {
+ if (!canGetLaunchedFrom()) {
+ return INVALID_UID;
+ }
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- return r != null ? r.launchedFromUid : android.os.Process.INVALID_UID;
+ return r != null ? r.launchedFromUid : INVALID_UID;
}
}
@Override
public String getLaunchedFromPackage(IBinder token) {
+ if (!canGetLaunchedFrom()) {
+ return null;
+ }
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
return r != null ? r.launchedFromPackage : null;
}
}
+ /** Whether the caller can get the package or uid that launched its activity. */
+ private boolean canGetLaunchedFrom() {
+ final int uid = Binder.getCallingUid();
+ if (UserHandle.getAppId(uid) == SYSTEM_UID) {
+ return true;
+ }
+ final PackageManagerInternal pm = mService.mWindowManager.mPmInternal;
+ final AndroidPackage callingPkg = pm.getPackage(uid);
+ if (callingPkg == null) {
+ return false;
+ }
+ if (callingPkg.isSignedWithPlatformKey()) {
+ return true;
+ }
+ final String[] installerNames = pm.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.getUserId(uid));
+ return installerNames.length > 0 && callingPkg.getPackageName().equals(installerNames[0]);
+ }
+
@Override
public void setRequestedOrientation(IBinder token, int requestedOrientation) {
final long origId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d1f2177..b52e527 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4157,7 +4157,7 @@
// The activity now gets access to the data associated with this Intent.
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants,
getUriPermissionsLocked());
- final ReferrerIntent rintent = new ReferrerIntent(intent, referrer);
+ final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer));
boolean unsent = true;
final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
@@ -8514,6 +8514,19 @@
}
/**
+ * Gets the referrer package name with respect to package visibility. This method returns null
+ * if the given package is not visible to this activity.
+ */
+ String getFilteredReferrer(String referrerPackage) {
+ if (referrerPackage == null || (!referrerPackage.equals(packageName)
+ && mWmService.mPmInternal.filterAppAccess(
+ referrerPackage, info.applicationInfo.uid, mUserId))) {
+ return null;
+ }
+ return referrerPackage;
+ }
+
+ /**
* Determines whether this ActivityRecord can turn the screen on. It checks whether the flag
* {@link ActivityRecord#getTurnScreenOnFlag} is set and checks whether the ActivityRecord
* should be visible depending on Keyguard state.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e3459a1..efa67e9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -848,9 +848,9 @@
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
- r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
- r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
- r.takeOptions(), isTransitionForward,
+ r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
+ proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
+ results, newIntents, r.takeOptions(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken,
r.getLaunchedFromBubble()));
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 73d31bf..1108c77 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -355,6 +355,8 @@
private PointerLocationView mPointerLocationView;
+ private int mDisplayCutoutTouchableRegionSize;
+
/**
* The area covered by system windows which belong to another display. Forwarded insets is set
* in case this is a virtual display, this is displayed on another display that has insets, and
@@ -1081,8 +1083,21 @@
(displayFrames, windowState, rect) -> {
rect.bottom = rect.top + getStatusBarHeight(displayFrames);
};
+ final TriConsumer<DisplayFrames, WindowState, Rect> gestureFrameProvider =
+ (displayFrames, windowState, rect) -> {
+ rect.bottom = rect.top + getStatusBarHeight(displayFrames);
+ final DisplayCutout cutout =
+ displayFrames.mInsetsState.getDisplayCutout();
+ if (cutout != null) {
+ final Rect top = cutout.getBoundingRectTop();
+ if (!top.isEmpty()) {
+ rect.bottom = rect.bottom + mDisplayCutoutTouchableRegionSize;
+ }
+ }
+ };
mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, frameProvider);
- mDisplayContent.setInsetProvider(ITYPE_TOP_MANDATORY_GESTURES, win, frameProvider);
+ mDisplayContent.setInsetProvider(
+ ITYPE_TOP_MANDATORY_GESTURES, win, gestureFrameProvider);
mDisplayContent.setInsetProvider(ITYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider);
break;
case TYPE_NAVIGATION_BAR:
@@ -1483,11 +1498,15 @@
statusBarBottom);
}
- sTmpRect.set(windowFrames.mFrame);
- sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
- sTmpRect.top = windowFrames.mFrame.top; // Ignore top display cutout inset
- sTmpRect.bottom = statusBarBottom; // Use collapsed status bar size
- contentFrame.set(sTmpRect);
+ final InsetsState state = displayFrames.mInsetsState;
+ sTmpRect.set(displayFrames.mDisplayCutoutSafe);
+ // The status bar content can extend into regular display cutout insets but not
+ // waterfall insets.
+ sTmpRect.top = Math.max(state.getDisplayCutout().getWaterfallInsets().top, 0);
+
+ contentFrame.set(windowFrames.mFrame);
+ contentFrame.intersect(sTmpRect);
+ contentFrame.bottom = statusBarBottom; // Use collapsed status bar size
}
private int layoutNavigationBar(DisplayFrames displayFrames, Rect contentFrame) {
@@ -1993,11 +2012,14 @@
mStatusBarHeightForRotation[landscapeRotation] =
mStatusBarHeightForRotation[seascapeRotation] =
res.getDimensionPixelSize(R.dimen.status_bar_height_landscape);
+ mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
+ R.dimen.display_cutout_touchable_region_size);
} else {
mStatusBarHeightForRotation[portraitRotation] =
mStatusBarHeightForRotation[upsideDownRotation] =
mStatusBarHeightForRotation[landscapeRotation] =
mStatusBarHeightForRotation[seascapeRotation] = 0;
+ mDisplayCutoutTouchableRegionSize = 0;
}
// Height of the navigation bar when presented horizontally at bottom
@@ -2980,6 +3002,7 @@
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
+ mSystemGestures.dump(pw, prefix);
pw.print(prefix); pw.println("Looper state:");
mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 73d6cec..c9db14d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -444,7 +444,9 @@
}
if (mDisplayContent.mFixedRotationTransitionListener
- .isTopFixedOrientationRecentsAnimating()) {
+ .isTopFixedOrientationRecentsAnimating()
+ // If screen is off or the device is going to sleep, then still allow to update.
+ && mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
// During the recents animation, the closing app might still be considered on top.
// In order to ignore its requested orientation to avoid a sensor led rotation (e.g
// user rotating the device while the recents animation is running), we ignore
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 18ea738b..aa257f8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -464,7 +464,8 @@
if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) {
// Only allow the extras to be dispatched to a global-intercepting drag target
ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
- DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, touchX, touchY,
+ DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
+ newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
data, false /* includeDragSurface */,
null /* dragAndDropPermission */);
try {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index da47328..ed1e784 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -90,6 +90,24 @@
onSourceChanged();
}
+ @Override
+ protected boolean updateClientVisibility(InsetsControlTarget caller) {
+ boolean changed = super.updateClientVisibility(caller);
+ if (changed && caller.getRequestedVisibility(mSource.getType())) {
+ reportImeDrawnForOrganizer(caller);
+ }
+ return changed;
+ }
+
+ private void reportImeDrawnForOrganizer(InsetsControlTarget caller) {
+ if (caller.getWindow() != null && caller.getWindow().getTask() != null) {
+ if (caller.getWindow().getTask().isOrganized()) {
+ mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask(
+ caller.getWindow().getTask());
+ }
+ }
+ }
+
private void onSourceChanged() {
if (mLastSource.equals(mSource)) {
return;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8c781a1..d417d56 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -39,6 +39,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -650,6 +651,7 @@
|| type == TYPE_DOCK_DIVIDER
|| type == TYPE_ACCESSIBILITY_OVERLAY
|| type == TYPE_INPUT_CONSUMER
- || type == TYPE_VOICE_INTERACTION;
+ || type == TYPE_VOICE_INTERACTION
+ || type == TYPE_STATUS_BAR_ADDITIONAL;
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index a7216da..054854a 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -106,13 +106,13 @@
}
/**
- * @return {@code true} for default display when AOD is showing. Otherwise, same as
- * {@link #isKeyguardOrAodShowing(int)}
+ * @return {@code true} for default display when AOD is showing, not going away. Otherwise, same
+ * as {@link #isKeyguardOrAodShowing(int)}
* TODO(b/125198167): Replace isKeyguardOrAodShowing() by this logic.
*/
boolean isKeyguardUnoccludedOrAodShowing(int displayId) {
if (displayId == DEFAULT_DISPLAY && mAodShowing) {
- return true;
+ return !mKeyguardGoingAway;
}
return isKeyguardOrAodShowing(displayId);
}
@@ -461,7 +461,7 @@
final KeyguardDisplayState state = getDisplayState(displayId);
if (isKeyguardUnoccludedOrAodShowing(displayId)) {
state.mSleepTokenAcquirer.acquire(displayId);
- } else if (!isKeyguardUnoccludedOrAodShowing(displayId)) {
+ } else {
state.mSleepTokenAcquirer.release(displayId);
}
}
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index b63843d..7bddb62 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -164,7 +164,19 @@
return 0;
}
- return w.mAttrs.preferredMinDisplayRefreshRate;
+ if (w.mAttrs.preferredMinDisplayRefreshRate > 0) {
+ return w.mAttrs.preferredMinDisplayRefreshRate;
+ }
+
+ String packageName = w.getOwningPackage();
+ // If app is using Camera, we set both the min and max refresh rate to the camera's
+ // preferred refresh rate to make sure we don't end up with a refresh rate lower
+ // than the camera capture rate, which will lead to dropping camera frames.
+ if (mNonHighRefreshRatePackages.contains(packageName)) {
+ return mLowRefreshRateMode.getRefreshRate();
+ }
+
+ return 0;
}
float getPreferredMaxRefreshRate(WindowState w) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bd688a6..52da4b8 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2125,6 +2125,9 @@
final Task rootTask;
if (singleActivity) {
rootTask = task;
+
+ // Apply the last recents animation leash transform to the task entering PIP
+ rootTask.maybeApplyLastRecentsAnimationTransaction();
} else {
// In the case of multiple activities, we will create a new task for it and then
// move the PIP activity into the task. Note that we explicitly defer the task
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3c6c23b..c7bf8ec 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -59,11 +59,30 @@
@VisibleForTesting
final Animatable mAnimatable;
private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
+
+ /**
+ * Static callback to run on all animations started through this SurfaceAnimator
+ * when an animation on a Surface is finished or cancelled without restart.
+ */
@VisibleForTesting
@Nullable
final OnAnimationFinishedCallback mStaticAnimationFinishedCallback;
+
+ /**
+ * Callback unique to each animation (i.e. AnimationAdapter). To be run when an animation on a
+ * Surface is finished or cancelled without restart.
+ */
@Nullable
- private OnAnimationFinishedCallback mAnimationFinishedCallback;
+ private OnAnimationFinishedCallback mSurfaceAnimationFinishedCallback;
+
+ /**
+ * The callback is triggered after the SurfaceAnimator sends a cancel call to the underlying
+ * AnimationAdapter.
+ * NOTE: Must be called wherever we call onAnimationCancelled on mAnimation.
+ */
+ @Nullable
+ private Runnable mAnimationCancelledCallback;
+
private boolean mAnimationStartDelayed;
/**
@@ -100,7 +119,7 @@
return;
}
final OnAnimationFinishedCallback animationFinishCallback =
- mAnimationFinishedCallback;
+ mSurfaceAnimationFinishedCallback;
reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */);
if (staticAnimationFinishedCallback != null) {
staticAnimationFinishedCallback.onAnimationFinished(type, anim);
@@ -130,15 +149,19 @@
* This is important as it will start with the leash hidden or visible before
* handing it to the component that is responsible to run the animation.
* @param animationFinishedCallback The callback being triggered when the animation finishes.
+ * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
+ * cancel call to the underlying AnimationAdapter.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
+ @Nullable Runnable animationCancelledCallback,
@Nullable SurfaceFreezer freezer) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
mAnimationType = type;
- mAnimationFinishedCallback = animationFinishedCallback;
+ mSurfaceAnimationFinishedCallback = animationFinishedCallback;
+ mAnimationCancelledCallback = animationCancelledCallback;
final SurfaceControl surface = mAnimatable.getSurfaceControl();
if (surface == null) {
Slog.w(TAG, "Unable to start animation, surface is null or no children.");
@@ -161,14 +184,9 @@
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
- @AnimationType int type,
- @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
- startAnimation(t, anim, hidden, type, animationFinishedCallback, null /* freezer */);
- }
-
- void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
- startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
+ startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */,
+ null /* animationCancelledCallback */, null /* freezer */);
}
/**
@@ -278,7 +296,8 @@
mLeash = from.mLeash;
mAnimation = from.mAnimation;
mAnimationType = from.mAnimationType;
- mAnimationFinishedCallback = from.mAnimationFinishedCallback;
+ mSurfaceAnimationFinishedCallback = from.mSurfaceAnimationFinishedCallback;
+ mAnimationCancelledCallback = from.mAnimationCancelledCallback;
// Cancel source animation, but don't let animation runner cancel the animation.
from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
@@ -306,11 +325,16 @@
final SurfaceControl leash = mLeash;
final AnimationAdapter animation = mAnimation;
final @AnimationType int animationType = mAnimationType;
- final OnAnimationFinishedCallback animationFinishedCallback = mAnimationFinishedCallback;
+ final OnAnimationFinishedCallback animationFinishedCallback =
+ mSurfaceAnimationFinishedCallback;
+ final Runnable animationCancelledCallback = mAnimationCancelledCallback;
reset(t, false);
if (animation != null) {
if (!mAnimationStartDelayed && forwardCancel) {
animation.onAnimationCancelled(leash);
+ if (animationCancelledCallback != null) {
+ animationCancelledCallback.run();
+ }
}
if (!restarting) {
if (mStaticAnimationFinishedCallback != null) {
@@ -335,7 +359,7 @@
private void reset(Transaction t, boolean destroyLeash) {
mService.mAnimationTransferMap.remove(mAnimation);
mAnimation = null;
- mAnimationFinishedCallback = null;
+ mSurfaceAnimationFinishedCallback = null;
mAnimationType = ANIMATION_TYPE_NONE;
if (mLeash == null) {
return;
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 513b1b7..658f4ef 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -16,6 +16,12 @@
package com.android.server.wm;
+import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
+import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
+import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
+import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
+
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -33,6 +39,8 @@
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.widget.OverScroller;
+import java.io.PrintWriter;
+
/**
* Listens for system-wide input gestures, firing callbacks when detected.
* @hide
@@ -54,7 +62,8 @@
private final Context mContext;
private final Handler mHandler;
private int mDisplayCutoutTouchableRegionSize;
- private int mSwipeStartThreshold;
+ // The thresholds for each edge of the display
+ private final Rect mSwipeStartThreshold = new Rect();
private int mSwipeDistanceThreshold;
private final Callbacks mCallbacks;
private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
@@ -66,7 +75,6 @@
int screenHeight;
int screenWidth;
- private DisplayInfo mTmpDisplayInfo = new DisplayInfo();
private int mDownPointers;
private boolean mSwipeFireable;
private boolean mDebugFireable;
@@ -88,27 +96,41 @@
void onConfigurationChanged() {
final Resources r = mContext.getResources();
+ final int defaultThreshold = r.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_start_threshold);
+ mSwipeStartThreshold.set(defaultThreshold, defaultThreshold, defaultThreshold,
+ defaultThreshold);
+ mSwipeDistanceThreshold = defaultThreshold;
+
final Display display = DisplayManagerGlobal.getInstance()
.getRealDisplay(Display.DEFAULT_DISPLAY);
- display.getDisplayInfo(mTmpDisplayInfo);
- mSwipeStartThreshold = mTmpDisplayInfo.logicalWidth > mTmpDisplayInfo.logicalHeight
- ? r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_landscape)
- : r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_portrait);
-
final DisplayCutout displayCutout = display.getCutout();
if (displayCutout != null) {
- final Rect bounds = displayCutout.getBoundingRectTop();
- if (!bounds.isEmpty()) {
- // Expand swipe start threshold such that we can catch touches that just start below
- // the notch area
- mDisplayCutoutTouchableRegionSize = r.getDimensionPixelSize(
- com.android.internal.R.dimen.display_cutout_touchable_region_size);
- mSwipeStartThreshold += mDisplayCutoutTouchableRegionSize;
+ // Expand swipe start threshold such that we can catch touches that just start beyond
+ // the notch area
+ mDisplayCutoutTouchableRegionSize = r.getDimensionPixelSize(
+ com.android.internal.R.dimen.display_cutout_touchable_region_size);
+ final Rect[] bounds = displayCutout.getBoundingRectsAll();
+ if (bounds[BOUNDS_POSITION_LEFT] != null) {
+ mSwipeStartThreshold.left = Math.max(mSwipeStartThreshold.left,
+ bounds[BOUNDS_POSITION_LEFT].width() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_TOP] != null) {
+ mSwipeStartThreshold.top = Math.max(mSwipeStartThreshold.top,
+ bounds[BOUNDS_POSITION_TOP].height() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_RIGHT] != null) {
+ mSwipeStartThreshold.right = Math.max(mSwipeStartThreshold.right,
+ bounds[BOUNDS_POSITION_RIGHT].width() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_BOTTOM] != null) {
+ mSwipeStartThreshold.bottom = Math.max(mSwipeStartThreshold.bottom,
+ bounds[BOUNDS_POSITION_BOTTOM].height()
+ + mDisplayCutoutTouchableRegionSize);
}
}
- mSwipeDistanceThreshold = mSwipeStartThreshold;
if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold
- + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
+ + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
}
private static <T> T checkNull(String name, T arg) {
@@ -275,22 +297,22 @@
final long elapsed = time - mDownTime[i];
if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
+ " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
- if (fromY <= mSwipeStartThreshold
+ if (fromY <= mSwipeStartThreshold.top
&& y > fromY + mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_TOP;
}
- if (fromY >= screenHeight - mSwipeStartThreshold
+ if (fromY >= screenHeight - mSwipeStartThreshold.bottom
&& y < fromY - mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_BOTTOM;
}
- if (fromX >= screenWidth - mSwipeStartThreshold
+ if (fromX >= screenWidth - mSwipeStartThreshold.right
&& x < fromX - mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_RIGHT;
}
- if (fromX <= mSwipeStartThreshold
+ if (fromX <= mSwipeStartThreshold.left
&& x > fromX + mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_LEFT;
@@ -298,6 +320,15 @@
return SWIPE_NONE;
}
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final String inner = prefix + " ";
+ pw.println(prefix + TAG + ":");
+ pw.print(inner); pw.print("mDisplayCutoutTouchableRegionSize=");
+ pw.println(mDisplayCutoutTouchableRegionSize);
+ pw.print(inner); pw.print("mSwipeStartThreshold="); pw.println(mSwipeStartThreshold);
+ pw.print(inner); pw.print("mSwipeDistanceThreshold="); pw.println(mSwipeDistanceThreshold);
+ }
+
private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener {
private OverScroller mOverscroller;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9634855..dca0bac 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2787,7 +2787,15 @@
if (inOutConfig.smallestScreenWidthDp
== Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
- if (WindowConfiguration.isFloating(windowingMode)) {
+ // When entering to or exiting from Pip, the PipTaskOrganizer will set the
+ // windowing mode of the activity in the task to WINDOWING_MODE_FULLSCREEN and
+ // temporarily set the bounds of the task to fullscreen size for transitioning.
+ // It will get the wrong value if the calculation is based on this temporary
+ // fullscreen bounds.
+ // We should just inherit the value from parent for this temporary state.
+ final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED
+ && !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds);
+ if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) {
// For floating tasks, calculate the smallest width from the bounds of the task
inOutConfig.smallestScreenWidthDp = (int) (
Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
@@ -5147,7 +5155,7 @@
/**
* @return true if the task is currently focused.
*/
- private boolean isFocused() {
+ boolean isFocused() {
if (mDisplayContent == null || mDisplayContent.mCurrentFocus == null) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index d450dbf..ee4c629 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -42,6 +42,9 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static java.lang.Integer.MIN_VALUE;
+
+import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
@@ -80,6 +83,22 @@
DisplayContent mDisplayContent;
/**
+ * A color layer that serves as a solid color background to certain animations.
+ */
+ private SurfaceControl mColorBackgroundLayer;
+
+ /**
+ * This counter is used to make sure we don't prematurely clear the background color in the
+ * case that background color animations are interleaved.
+ * NOTE: The last set color will remain until the counter is reset to 0, which means that an
+ * animation background color may sometime remain after the animation has finished through an
+ * animation with a different background color if an animation starts after and ends before
+ * another where both set different background colors. However, this is not a concern as
+ * currently all task animation backgrounds are the same color.
+ */
+ private int mColorLayerCounter = 0;
+
+ /**
* A control placed at the appropriate level for transitions to occur.
*/
private SurfaceControl mAppAnimationLayer;
@@ -961,6 +980,11 @@
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
if (getParent() != null) {
super.onParentChanged(newParent, oldParent, () -> {
+ mColorBackgroundLayer = makeChildSurface(null)
+ .setColorLayer()
+ .setName("colorBackgroundLayer")
+ .setCallsite("TaskDisplayArea.onParentChanged")
+ .build();
mAppAnimationLayer = makeChildSurface(null)
.setName("animationLayer")
.setCallsite("TaskDisplayArea.onParentChanged")
@@ -977,6 +1001,7 @@
.setName("splitScreenDividerAnchor")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
+
getSyncTransaction()
.show(mAppAnimationLayer)
.show(mBoostedAppAnimationLayer)
@@ -986,11 +1011,13 @@
} else {
super.onParentChanged(newParent, oldParent);
mWmService.mTransactionFactory.get()
+ .remove(mColorBackgroundLayer)
.remove(mAppAnimationLayer)
.remove(mBoostedAppAnimationLayer)
.remove(mHomeAppAnimationLayer)
.remove(mSplitScreenDividerAnchor)
.apply();
+ mColorBackgroundLayer = null;
mAppAnimationLayer = null;
mBoostedAppAnimationLayer = null;
mHomeAppAnimationLayer = null;
@@ -998,6 +1025,39 @@
}
}
+ void setBackgroundColor(@ColorInt int color) {
+ if (mColorBackgroundLayer == null) {
+ return;
+ }
+
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color >> 0) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+
+ mColorLayerCounter++;
+
+ getPendingTransaction().setLayer(mColorBackgroundLayer, MIN_VALUE)
+ .setColor(mColorBackgroundLayer, new float[]{r, g, b})
+ .setAlpha(mColorBackgroundLayer, a)
+ .setWindowCrop(mColorBackgroundLayer, getSurfaceWidth(), getSurfaceHeight())
+ .setPosition(mColorBackgroundLayer, 0, 0)
+ .show(mColorBackgroundLayer);
+
+ scheduleAnimation();
+ }
+
+ void clearBackgroundColor() {
+ mColorLayerCounter--;
+
+ // Only clear the color layer if we have received the same amounts of clear as set
+ // requests.
+ if (mColorLayerCounter == 0) {
+ getPendingTransaction().hide(mColorBackgroundLayer);
+ scheduleAnimation();
+ }
+ }
+
@Override
void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
super.migrateToNewSurfaceControl(t);
@@ -1006,6 +1066,7 @@
}
// As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
+ t.reparent(mColorBackgroundLayer, mSurfaceControl);
t.reparent(mAppAnimationLayer, mSurfaceControl);
t.reparent(mBoostedAppAnimationLayer, mSurfaceControl);
t.reparent(mHomeAppAnimationLayer, mSurfaceControl);
@@ -2149,6 +2210,11 @@
}
@Override
+ TaskDisplayArea getTaskDisplayArea() {
+ return this;
+ }
+
+ @Override
boolean isTaskDisplayArea() {
return true;
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 09c5581..88467ba 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -733,6 +733,17 @@
mPendingTaskEvents.clear();
}
+ void reportImeDrawnOnTask(Task task) {
+ final TaskOrganizerState state = mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
+ if (state != null) {
+ try {
+ state.mOrganizer.mTaskOrganizer.onImeDrawnOnTask(task.mTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onImeDrawnOnTask callback", e);
+ }
+ }
+ }
+
void onTaskInfoChanged(Task task, boolean force) {
if (!task.mTaskAppearedSent) {
// Skip if task still not appeared.
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index ec4eb5d..073a508 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -118,16 +118,30 @@
@Override
public long calculateStatusBarTransitionStartTime() {
TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
- if (openTranslateAnimation != null) {
- // Some interpolators are extremely quickly mostly finished, but not completely. For
- // our purposes, we need to find the fraction for which ther interpolator is mostly
- // there, and use that value for the calculation.
- float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
- return SystemClock.uptimeMillis()
- + openTranslateAnimation.getStartOffset()
- + (long)(openTranslateAnimation.getDuration() * t)
- - STATUS_BAR_TRANSITION_DURATION;
+ if (openTranslateAnimation != null) {
+ if (openTranslateAnimation.isXAxisTransition()
+ && openTranslateAnimation.isFullWidthTranslate()) {
+ // On X axis transitions that are fullscreen (heuristic for task like transitions)
+ // we want the status bar to animate right in the middle of the translation when
+ // the windows/tasks have each moved half way across.
+ float t = findMiddleOfTranslationFraction(openTranslateAnimation.getInterpolator());
+
+ return SystemClock.uptimeMillis()
+ + openTranslateAnimation.getStartOffset()
+ + (long) (openTranslateAnimation.getDuration() * t)
+ - (long) (STATUS_BAR_TRANSITION_DURATION * 0.5);
+ } else {
+ // Some interpolators are extremely quickly mostly finished, but not completely. For
+ // our purposes, we need to find the fraction for which their interpolator is mostly
+ // there, and use that value for the calculation.
+ float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
+
+ return SystemClock.uptimeMillis()
+ + openTranslateAnimation.getStartOffset()
+ + (long) (openTranslateAnimation.getDuration() * t)
+ - STATUS_BAR_TRANSITION_DURATION;
+ }
} else {
return SystemClock.uptimeMillis();
}
@@ -176,20 +190,39 @@
}
/**
- * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
- * {@code interpolator(t + eps) > 0.99}.
+ * Finds the fraction of the animation's duration at which the transition is almost done with a
+ * maximal error of 0.01 when it is animated with {@code interpolator}.
*/
private static float findAlmostThereFraction(Interpolator interpolator) {
+ return findInterpolationAdjustedTargetFraction(interpolator, 0.99f, 0.01f);
+ }
+
+ /**
+ * Finds the fraction of the animation's duration at which the transition is spacially half way
+ * done with a maximal error of 0.01 when it is animated with {@code interpolator}.
+ */
+ private float findMiddleOfTranslationFraction(Interpolator interpolator) {
+ return findInterpolationAdjustedTargetFraction(interpolator, 0.5f, 0.01f);
+ }
+
+ /**
+ * Binary searches for a {@code val} such that there exists an {@code -0.01 < epsilon < 0.01}
+ * for which {@code interpolator(val + epsilon) > target}.
+ */
+ private static float findInterpolationAdjustedTargetFraction(
+ Interpolator interpolator, float target, float epsilon) {
float val = 0.5f;
float adj = 0.25f;
- while (adj >= 0.01f) {
- if (interpolator.getInterpolation(val) < 0.99f) {
+
+ while (adj >= epsilon) {
+ if (interpolator.getInterpolation(val) < target) {
val += adj;
} else {
val -= adj;
}
adj /= 2;
}
+
return val;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b1c7e19..d99aed1 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -30,6 +30,10 @@
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -59,10 +63,13 @@
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
+import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.app.WindowConfiguration;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
@@ -88,6 +95,7 @@
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
@@ -850,6 +858,12 @@
return parent != null ? parent.getRootDisplayArea() : null;
}
+ @Nullable
+ TaskDisplayArea getTaskDisplayArea() {
+ WindowContainer parent = getParent();
+ return parent != null ? parent.getTaskDisplayArea() : null;
+ }
+
boolean isAttached() {
return getDisplayArea() != null;
}
@@ -2495,10 +2509,13 @@
* some point but the meaning is too weird to work for all containers.
* @param type The type of animation defined as {@link AnimationType}.
* @param animationFinishedCallback The callback being triggered when the animation finishes.
+ * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
+ * cancel call to the underlying AnimationAdapter.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
- @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+ @Nullable OnAnimationFinishedCallback animationFinishedCallback,
+ @Nullable Runnable animationCancelledCallback) {
if (DEBUG_ANIM) {
Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
}
@@ -2506,7 +2523,14 @@
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
- mSurfaceFreezer);
+ animationCancelledCallback, mSurfaceFreezer);
+ }
+
+ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+ @AnimationType int type,
+ @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+ startAnimation(t, anim, hidden, type, animationFinishedCallback,
+ null /* adapterAnimationCancelledCallback */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@@ -2714,8 +2738,26 @@
if (sources != null) {
mSurfaceAnimationSources.addAll(sources);
}
+
+ TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
+ boolean isSettingBackgroundColor = taskDisplayArea != null
+ && isTransitionWithBackgroundColor(transit);
+
+ if (isSettingBackgroundColor) {
+ Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+ @ColorInt int backgroundColor = uiContext.getColor(R.color.overview_background);
+
+ taskDisplayArea.setBackgroundColor(backgroundColor);
+ }
+
+ final Runnable cleanUpCallback = isSettingBackgroundColor
+ ? taskDisplayArea::clearBackgroundColor : () -> {};
+
startAnimation(getPendingTransaction(), adapter, !isVisible(),
- ANIMATION_TYPE_APP_TRANSITION);
+ ANIMATION_TYPE_APP_TRANSITION,
+ (type, anim) -> cleanUpCallback.run(),
+ cleanUpCallback);
+
if (adapter.getShowWallpaper()) {
getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
@@ -2726,6 +2768,13 @@
}
}
+ private boolean isTransitionWithBackgroundColor(@TransitionOldType int transit) {
+ return transit == TRANSIT_OLD_TASK_OPEN
+ || transit == TRANSIT_OLD_TASK_CLOSE
+ || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_TASK_TO_BACK;
+ }
+
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
return mWmService.mSurfaceAnimationRunner;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3421b28..68e85c8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2727,8 +2727,8 @@
}
@Override
- public boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
- Bundle options) {
+ public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int
+ type, int displayId, Bundle options) {
final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
"attachWindowContextToDisplayArea", false /* printLog */);
final int callingUid = Binder.getCallingUid();
@@ -2739,15 +2739,17 @@
if (dc == null) {
ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayArea: trying to attach"
+ " to a non-existing display:%d", displayId);
- return false;
+ return null;
}
// TODO(b/155340867): Investigate if we still need roundedCornerOverlay after
// the feature b/155340867 is completed.
final DisplayArea da = dc.findAreaForWindowType(type, options,
callerCanManageAppTokens, false /* roundedCornerOverlay */);
+ // TODO(b/190019118): Avoid to send onConfigurationChanged because it has been done
+ // in return value of attachWindowContextToDisplayArea.
mWindowContextListenerController.registerWindowContainerListener(clientToken, da,
callingUid, type, options);
- return true;
+ return da.getConfiguration();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7119,6 +7121,7 @@
"requestScrollCapture: caught exception dispatching to window."
+ "token=%s", targetWindow.mClient.asBinder());
responseBuilder.setWindowTitle(targetWindow.getName());
+ responseBuilder.setPackageName(targetWindow.getOwningPackage());
responseBuilder.setDescription(String.format("caught exception: %s", e));
listener.onScrollCaptureResponse(responseBuilder.build());
}
@@ -8646,8 +8649,9 @@
if (imeTargetWindowTask == null) {
return false;
}
- final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId,
- false /* isLowResolution */);
+ final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
+ imeTargetWindowTask.mUserId, false /* isLowResolution */,
+ false /* restoreFromDisk */);
return snapshot != null && snapshot.hasImeSurface();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5e042ef..0af6a29 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -26,7 +26,6 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
-import static android.hardware.input.InputManager.BLOCK_UNTRUSTED_TOUCHES;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -194,7 +193,6 @@
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
-import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -1156,9 +1154,6 @@
}
int getTouchOcclusionMode() {
- if (!CompatChanges.isChangeEnabled(BLOCK_UNTRUSTED_TOUCHES, mOwnerUid)) {
- return TouchOcclusionMode.ALLOW;
- }
if (WindowManager.LayoutParams.isSystemAlertWindowType(mAttrs.type)) {
return TouchOcclusionMode.USE_OPACITY;
}
diff --git a/services/core/jni/com_android_server_ConsumerIrService.cpp b/services/core/jni/com_android_server_ConsumerIrService.cpp
index 2ca348b..63daa35 100644
--- a/services/core/jni/com_android_server_ConsumerIrService.cpp
+++ b/services/core/jni/com_android_server_ConsumerIrService.cpp
@@ -34,7 +34,7 @@
static sp<IConsumerIr> mHal;
-static jboolean halOpen(JNIEnv* /* env */, jobject /* obj */) {
+static jboolean getHidlHalService(JNIEnv * /* env */, jobject /* obj */) {
// TODO(b/31632518)
mHal = IConsumerIr::getService();
return mHal != nullptr;
@@ -84,9 +84,9 @@
}
static const JNINativeMethod method_table[] = {
- { "halOpen", "()Z", (void *)halOpen },
- { "halTransmit", "(I[I)I", (void *)halTransmit },
- { "halGetCarrierFrequencies", "()[I", (void *)halGetCarrierFrequencies},
+ {"getHidlHalService", "()Z", (void *)getHidlHalService},
+ {"halTransmit", "(I[I)I", (void *)halTransmit},
+ {"halGetCarrierFrequencies", "()[I", (void *)halGetCarrierFrequencies},
};
int register_android_server_ConsumerIrService(JNIEnv *env) {
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index e319e3f..4190a91 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -55,6 +55,7 @@
#define SYNC_RECEIVED_WHILE_FROZEN (1)
#define ASYNC_RECEIVED_WHILE_FROZEN (2)
+#define TXNS_PENDING_WHILE_FROZEN (4)
namespace android {
@@ -232,17 +233,20 @@
compactProcessOrFallback(pid, compactionFlags);
}
-static void com_android_server_am_CachedAppOptimizer_freezeBinder(
+static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
- if (IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */) != 0) {
+ jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
+ if (retVal != 0 && retVal != -EAGAIN) {
jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
}
+
+ return retVal;
}
static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
jobject clazz, jint pid) {
- bool syncReceived = false, asyncReceived = false;
+ uint32_t syncReceived = 0, asyncReceived = 0;
int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
@@ -252,13 +256,12 @@
jint retVal = 0;
- if(syncReceived) {
- retVal |= SYNC_RECEIVED_WHILE_FROZEN;;
- }
-
- if(asyncReceived) {
- retVal |= ASYNC_RECEIVED_WHILE_FROZEN;
- }
+ // bit 0 of sync_recv goes to bit 0 of retVal
+ retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN;
+ // bit 0 of async_recv goes to bit 1 of retVal
+ retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN;
+ // bit 1 of sync_recv goes to bit 2 of retVal
+ retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN;
return retVal;
}
@@ -278,7 +281,7 @@
/* name, signature, funcPtr */
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
- {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+ {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
{"getBinderFreezeInfo", "(I)I",
(void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
{"getFreezerCheckPath", "()Ljava/lang/String;",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a94ad4a..bb9740b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -337,7 +337,7 @@
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
- void setPointerCapture(bool enabled) override;
+ void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
/* --- PointerControllerPolicyInterface implementation --- */
@@ -372,8 +372,8 @@
// Show touches feature enable/disable.
bool showTouches;
- // Pointer capture feature enable/disable.
- bool pointerCapture;
+ // The latest request to enable or disable Pointer Capture.
+ PointerCaptureRequest pointerCaptureRequest;
// Sprite controller singleton, created on first use.
sp<SpriteController> spriteController;
@@ -417,7 +417,6 @@
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
- mLocked.pointerCapture = false;
mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
}
mInteractive = true;
@@ -446,7 +445,9 @@
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
- dump += StringPrintf(INDENT "Pointer Capture Enabled: %s\n", toString(mLocked.pointerCapture));
+ dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
+ mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.seq);
}
dump += "\n";
@@ -634,7 +635,7 @@
outConfig->showTouches = mLocked.showTouches;
- outConfig->pointerCapture = mLocked.pointerCapture;
+ outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
outConfig->setDisplayViewports(mLocked.viewports);
@@ -1383,16 +1384,16 @@
checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus");
}
-void NativeInputManager::setPointerCapture(bool enabled) {
+void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) {
{ // acquire lock
AutoMutex _l(mLock);
- if (mLocked.pointerCapture == enabled) {
+ if (mLocked.pointerCaptureRequest == request) {
return;
}
- ALOGV("%s pointer capture.", enabled ? "Enabling" : "Disabling");
- mLocked.pointerCapture = enabled;
+ ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ mLocked.pointerCaptureRequest = request;
} // release lock
mInputManager->getReader()->requestRefreshConfiguration(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ac344d6..9ceabce 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3599,6 +3599,9 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(
+ isCallingFromPackage(adminReceiver.getPackageName(), caller.getUid())
+ || isSystemUid(caller));
synchronized (getLockObject()) {
ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
@@ -12695,74 +12698,21 @@
// This method is called from AM with its lock held, so don't take the DPMS lock.
// b/29242568
- ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
- if (profileOwner != null) {
- return DevicePolicyManagerService.this
- .createShowAdminSupportIntent(profileOwner, userId);
- }
-
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null && deviceOwner.first == userId) {
- return DevicePolicyManagerService.this
- .createShowAdminSupportIntent(deviceOwner.second, userId);
- }
-
- // We're not specifying the device admin because there isn't one.
- if (useDefaultIfNoAdmin) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
+ if (getEnforcingAdminAndUserDetailsInternal(userId, null) != null
+ || useDefaultIfNoAdmin) {
+ return DevicePolicyManagerService.this.createShowAdminSupportIntent(userId);
}
return null;
}
@Override
public Intent createUserRestrictionSupportIntent(int userId, String userRestriction) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- final List<UserManager.EnforcingUser> sources = mUserManager
- .getUserRestrictionSources(userRestriction, UserHandle.of(userId));
- if (sources == null || sources.isEmpty()) {
- // The restriction is not enforced.
- return null;
- } else if (sources.size() > 1) {
- // In this case, we'll show an admin support dialog that does not
- // specify the admin.
- // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
- // the admin for the calling user.
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- null, userId);
- }
- final UserManager.EnforcingUser enforcingUser = sources.get(0);
- final int sourceType = enforcingUser.getUserRestrictionSource();
- final int enforcingUserId = enforcingUser.getUserHandle().getIdentifier();
- if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) {
- // Restriction was enforced by PO
- final ComponentName profileOwner = mOwners.getProfileOwnerComponent(
- enforcingUserId);
- if (profileOwner != null) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- profileOwner, enforcingUserId);
- }
- } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
- // Restriction was enforced by DO
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- deviceOwner.second, deviceOwner.first);
- }
- } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) {
- /*
- * In this case, the user restriction is enforced by the system.
- * So we won't show an admin support intent, even if it is also
- * enforced by a profile/device owner.
- */
- return null;
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
+ Intent intent = null;
+ if (getEnforcingAdminAndUserDetailsInternal(userId, userRestriction) != null) {
+ intent = DevicePolicyManagerService.this.createShowAdminSupportIntent(userId);
+ intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, userRestriction);
}
- return null;
+ return intent;
}
@Override
@@ -13057,53 +13007,153 @@
}
}
- private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
+ private Intent createShowAdminSupportIntent(int userId) {
// This method is called with AMS lock held, so don't take DPMS lock
final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
intent.putExtra(Intent.EXTRA_USER_ID, userId);
- intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
return intent;
}
- @Override
- public Intent createAdminSupportIntent(String restriction) {
- Objects.requireNonNull(restriction);
- final CallerIdentity caller = getCallerIdentity();
- Intent intent = null;
- if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction) ||
- DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ */
+ private Bundle getEnforcingAdminAndUserDetailsInternal(int userId, String restriction) {
+ Bundle result = null;
+ if (restriction == null) {
+ ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+ if (profileOwner != null) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ profileOwner);
+ return result;
+ }
+ final Pair<Integer, ComponentName> deviceOwner =
+ mOwners.getDeviceOwnerUserIdAndComponent();
+ if (deviceOwner != null && deviceOwner.first == userId) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ deviceOwner.second);
+ return result;
+ }
+ } else if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)
+ || DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
synchronized (getLockObject()) {
- final DevicePolicyData policy = getUserData(caller.getUserId());
+ final DevicePolicyData policy = getUserData(userId);
final int N = policy.mAdminList.size();
for (int i = 0; i < N; i++) {
final ActiveAdmin admin = policy.mAdminList.get(i);
if ((admin.disableCamera &&
- DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) ||
- (admin.disableScreenCapture && DevicePolicyManager
- .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
- intent = createShowAdminSupportIntent(admin.info.getComponent(),
- caller.getUserId());
- break;
+ DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction))
+ || (admin.disableScreenCapture && DevicePolicyManager
+ .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ admin.info.getComponent());
+ return result;
}
}
// For the camera, a device owner on a different user can disable it globally,
// so we need an additional check.
- if (intent == null
+ if (result == null
&& DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
final ActiveAdmin admin = getDeviceOwnerAdminLocked();
if (admin != null && admin.disableCamera) {
- intent = createShowAdminSupportIntent(admin.info.getComponent(),
- mOwners.getDeviceOwnerUserId());
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, mOwners.getDeviceOwnerUserId());
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ admin.info.getComponent());
+ return result;
}
}
}
} else {
- // if valid, |restriction| can only be a user restriction
- intent = mLocalService.createUserRestrictionSupportIntent(caller.getUserId(),
- restriction);
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ List<UserManager.EnforcingUser> sources = mUserManager
+ .getUserRestrictionSources(restriction, UserHandle.of(userId));
+ if (sources == null || sources.isEmpty()) {
+ // The restriction is not enforced.
+ return null;
+ } else if (sources.size() > 1) {
+ // In this case, we'll show an admin support dialog that does not
+ // specify the admin.
+ // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
+ // the admin for the calling user.
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ return result;
+ }
+ final UserManager.EnforcingUser enforcingUser = sources.get(0);
+ final int sourceType = enforcingUser.getUserRestrictionSource();
+ final int enforcingUserId = enforcingUser.getUserHandle().getIdentifier();
+ if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) {
+ // Restriction was enforced by PO
+ final ComponentName profileOwner = mOwners.getProfileOwnerComponent(
+ enforcingUserId);
+ if (profileOwner != null) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, enforcingUserId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ profileOwner);
+ return result;
+ }
+ } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
+ // Restriction was enforced by DO
+ final Pair<Integer, ComponentName> deviceOwner =
+ mOwners.getDeviceOwnerUserIdAndComponent();
+ if (deviceOwner != null) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, deviceOwner.first);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ deviceOwner.second);
+ return result;
+ }
+ } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) {
+ /*
+ * In this case, the user restriction is enforced by the system.
+ * So we won't show an admin support intent, even if it is also
+ * enforced by a profile/device owner.
+ */
+ return null;
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
- if (intent != null) {
+ return null;
+ }
+
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @return Details of admin and user which enforced the restriction for the userId.
+ */
+ @Override
+ public Bundle getEnforcingAdminAndUserDetails(int userId, String restriction) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+ return getEnforcingAdminAndUserDetailsInternal(userId, restriction);
+ }
+
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ */
+ @Override
+ public Intent createAdminSupportIntent(String restriction) {
+ Objects.requireNonNull(restriction);
+ final CallerIdentity caller = getCallerIdentity();
+ final int userId = caller.getUserId();
+ Intent intent = null;
+ if (getEnforcingAdminAndUserDetailsInternal(userId, restriction) != null) {
+ intent = createShowAdminSupportIntent(userId);
intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, restriction);
}
return intent;
@@ -14061,6 +14111,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
Preconditions.checkCallAuthorization(isManagedProfile(userHandle),
"You can not get organization name outside a managed profile, userId = %d",
userHandle);
diff --git a/services/incremental/OWNERS b/services/incremental/OWNERS
index 7ebb962..fe8c253 100644
--- a/services/incremental/OWNERS
+++ b/services/incremental/OWNERS
@@ -3,6 +3,5 @@
alexbuy@google.com
schfan@google.com
-toddke@google.com
zyy@google.com
patb@google.com
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1f96c66..2aee778 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -440,7 +440,6 @@
private static final String SYSPROP_START_UPTIME = "sys.system_server.start_uptime";
private Future<?> mZygotePreload;
- private Future<?> mBlobStoreServiceStart;
private final SystemServerDumper mDumper = new SystemServerDumper();
@@ -2255,12 +2254,9 @@
t.traceEnd();
}
- mBlobStoreServiceStart = SystemServerInitThreadPool.submit(() -> {
- final TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
- traceLog.traceBegin(START_BLOB_STORE_SERVICE);
- mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
- traceLog.traceEnd();
- }, START_BLOB_STORE_SERVICE);
+ t.traceBegin(START_BLOB_STORE_SERVICE);
+ mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
// Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
t.traceBegin("StartDreamManager");
@@ -2670,9 +2666,6 @@
mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
t.traceEnd();
- ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
- START_BLOB_STORE_SERVICE);
-
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
diff --git a/services/proguard.flags b/services/proguard.flags
index 30dd6cf..5d01d3e 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -1,11 +1,21 @@
# TODO(b/196084106): Refine and optimize this configuration. Note that this
# configuration is only used when `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA=true`.
-keep,allowoptimization,allowaccessmodification class ** {
- *;
+ !synthetic *;
}
# Various classes subclassed in ethernet-service (avoid marking final).
-keep public class android.net.** { *; }
# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing).
--keep public class com.android.server.utils.Slogf { *; }
\ No newline at end of file
+-keep public class com.android.server.utils.Slogf { *; }
+
+# Allows making private and protected methods/fields public as part of
+# optimization. This enables inlining of trivial getter/setter methods.
+-allowaccessmodification
+
+# Disallow accessmodification for soundtrigger classes. Logging via reflective
+# public member traversal can cause infinite loops. See b/210901706.
+-keep,allowoptimization class com.android.server.soundtrigger_middleware.** {
+ !synthetic *;
+}
diff --git a/services/tests/PackageManager/packageinstaller/Android.bp b/services/tests/PackageManager/packageinstaller/Android.bp
new file mode 100644
index 0000000..35d754b
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "PackageInstallerTests",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "junit",
+ "kotlin-test",
+ "truth-prebuilt",
+ ],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/services/tests/PackageManager/packageinstaller/AndroidManifest.xml b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml
new file mode 100644
index 0000000..d706258
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.packageinstaller.test">
+
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.packageinstaller.test"
+ />
+
+</manifest>
+
diff --git a/services/tests/PackageManager/packageinstaller/AndroidTest.xml b/services/tests/PackageManager/packageinstaller/AndroidTest.xml
new file mode 100644
index 0000000..c39285ff
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Test module config for PackageInstallerTests">
+ <option name="test-tag" value="PackageInstallerTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="PackageInstallerTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.packageinstaller.test" />
+ </test>
+</configuration>
diff --git a/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt
new file mode 100644
index 0000000..d7d2726
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.test
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+
+class ExportedComponentTest {
+
+ private val context: Context = InstrumentationRegistry.getContext()
+
+ @Test
+ fun verifyNoExportedReceivers() {
+ val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
+ data = Uri.parse("content://mockForTest")
+ }
+ val packageInstallers = context.packageManager.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY or PackageManager.MATCH_DISABLED_COMPONENTS)
+ .map { it.activityInfo.packageName }
+ .distinct()
+ .map { context.packageManager.getPackageInfo(it, PackageManager.GET_RECEIVERS) }
+
+ assertThat(packageInstallers).isNotEmpty()
+
+ packageInstallers.forEach {
+ val exported = it.receivers.filter { it.exported }
+ assertWithMessage("Receivers should not be exported").that(exported).isEmpty()
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 17a5dcc..3cab5ec 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.MANAGE_APPOPS"/>
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml
new file mode 100644
index 0000000..a4de08a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="true" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml
new file mode 100644
index 0000000..47649d7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml
new file mode 100644
index 0000000..4fd9ebf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="false" />
+ <individual-sensor-privacy sensor="2" enabled="true" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml
new file mode 100644
index 0000000..e8f9edf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="false" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index acf50b45..d710308 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -323,6 +323,7 @@
when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
doNothing().when(mWakeLock).acquire();
doNothing().when(mAlarmManager).set(anyInt(), anyLong(), anyString(), any(), any());
+ doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any());
doNothing().when(mAlarmManager)
.setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any());
doReturn(mock(Sensor.class)).when(mSensorManager)
@@ -1046,7 +1047,8 @@
verifyLightStateConditions(LIGHT_STATE_IDLE);
inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT),
- longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX));
+ longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
+ eq(false));
// Should just alternate between IDLE and IDLE_MAINTENANCE now.
@@ -1054,19 +1056,22 @@
verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET),
- longThat(l -> l == mConstants.FLEX_TIME_SHORT));
+ longThat(l -> l == mConstants.FLEX_TIME_SHORT),
+ eq(true));
mDeviceIdleController.stepLightIdleStateLocked("testing");
verifyLightStateConditions(LIGHT_STATE_IDLE);
inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT),
- longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX));
+ longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
+ eq(false));
mDeviceIdleController.stepLightIdleStateLocked("testing");
verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET),
- longThat(l -> l == mConstants.FLEX_TIME_SHORT));
+ longThat(l -> l == mConstants.FLEX_TIME_SHORT),
+ eq(true));
// Test that motion doesn't reset the idle timeout.
mDeviceIdleController.handleMotionDetectedLocked(50, "test");
@@ -1075,7 +1080,8 @@
verifyLightStateConditions(LIGHT_STATE_IDLE);
inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT),
- longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX));
+ longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
+ eq(false));
}
///////////////// EXIT conditions ///////////////////
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index fd364ae7..51fa851 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -37,6 +37,8 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Bundle;
import android.os.RecoverySystem;
@@ -83,6 +85,8 @@
private static final String CALLING_PACKAGE1 = "com.package.name1";
private static final String CALLING_PACKAGE2 = "com.package.name2";
private static final String CALLING_PACKAGE3 = "com.package.name3";
+ private static final String PERSISTENT_PACKAGE = "com.persistent.package";
+ private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package";
private static final String NAMESPACE1 = "namespace1";
private static final String NAMESPACE2 = "namespace2";
private static final String NAMESPACE3 = "namespace3";
@@ -103,6 +107,8 @@
private PackageWatchdog mMockPackageWatchdog;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ContentResolver mMockContentResolver;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageManager mPackageManager;
@Captor
private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor;
@@ -129,6 +135,17 @@
mNamespacesWiped = new HashSet<>();
when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ ApplicationInfo persistentApplicationInfo = new ApplicationInfo();
+ persistentApplicationInfo.flags |=
+ ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT;
+
+ // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and
+ // system. Don't set any flags otherwise.
+ when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE),
+ anyInt())).thenReturn(persistentApplicationInfo);
+ when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE),
+ anyInt())).thenReturn(new ApplicationInfo());
// Reset observer instance to get new mock context on every run
RescuePartyObserver.reset();
@@ -241,29 +258,53 @@
@Test
public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
- notePersistentAppCrash(1);
+ noteAppCrash(1, true);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
- notePersistentAppCrash(2);
+ noteAppCrash(2, true);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
- notePersistentAppCrash(3);
+ noteAppCrash(3, true);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
- notePersistentAppCrash(4);
+ noteAppCrash(4, true);
assertTrue(RescueParty.isRebootPropertySet());
- notePersistentAppCrash(5);
+ noteAppCrash(5, true);
assertTrue(RescueParty.isFactoryResetPropertySet());
}
@Test
+ public void testNonPersistentAppOnlyPerformsFlagResets() {
+ noteAppCrash(1, false);
+
+ verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
+ /*configResetVerifiedTimesMap=*/ null);
+
+ noteAppCrash(2, false);
+
+ verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
+ /*configResetVerifiedTimesMap=*/ null);
+
+ noteAppCrash(3, false);
+
+ verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
+ /*configResetVerifiedTimesMap=*/ null);
+
+ noteAppCrash(4, false);
+ assertFalse(RescueParty.isRebootPropertySet());
+
+ noteAppCrash(5, false);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
public void testNonPersistentAppCrashDetectionWithScopedResets() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
@@ -311,11 +352,11 @@
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
- assertTrue(RescueParty.isRebootPropertySet());
+ assertFalse(RescueParty.isRebootPropertySet());
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
- assertTrue(RescueParty.isFactoryResetPropertySet());
+ assertFalse(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -376,11 +417,11 @@
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
- assertTrue(RescueParty.isRebootPropertySet());
+ assertFalse(RescueParty.isRebootPropertySet());
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
- assertTrue(RescueParty.isFactoryResetPropertySet());
+ assertFalse(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -627,9 +668,10 @@
RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
}
- private void notePersistentAppCrash(int mitigationCount) {
+ private void noteAppCrash(int mitigationCount, boolean isPersistent) {
+ String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
- "com.package.name", 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
+ packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
}
private Bundle getConfigAccessBundle(String callingPackage, String namespace) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index 022fadc..609768c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -21,12 +21,16 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import android.app.ActivityManager;
+import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
+import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -48,8 +52,14 @@
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
-import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -60,8 +70,11 @@
* Build/Install/Run:
* atest FrameworksMockingServicesTests:CacheOomRankerTest
*/
+@SuppressWarnings("GuardedBy") // No tests are concurrent, so no need to test locking.
@RunWith(MockitoJUnitRunner.class)
public class CacheOomRankerTest {
+ private static final Instant NOW = LocalDate.of(2021, 1, 1).atStartOfDay(
+ ZoneOffset.UTC).toInstant();
@Mock
private AppOpsService mAppOpsService;
@@ -82,6 +95,7 @@
private int mNextUid = 30000;
private int mNextPackageUid = 40000;
private int mNextPackageName = 1;
+ private Map<Integer, Long> mPidToRss;
private TestExecutor mExecutor = new TestExecutor();
private CacheOomRanker mCacheOomRanker;
@@ -107,7 +121,15 @@
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
- mCacheOomRanker = new CacheOomRanker(mAms);
+ mPidToRss = new HashMap<>();
+ mCacheOomRanker = new CacheOomRanker(
+ mAms,
+ pid -> {
+ Long rss = mPidToRss.get(pid);
+ assertThat(rss).isNotNull();
+ return new long[]{rss};
+ }
+ );
mCacheOomRanker.init(mExecutor);
}
@@ -136,6 +158,15 @@
mExecutor.init();
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS,
+ Integer.toString(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1),
+ false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.mPreserveTopNApps)
+ .isEqualTo(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1);
+
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f),
false);
@@ -165,6 +196,9 @@
@Test
public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 5,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 0.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */1.0f);
@@ -172,36 +206,40 @@
ProcessList list = new ProcessList();
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(lastUsed40MinutesAgo);
ProcessRecord lastUsed42MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(lastUsed42MinutesAgo);
ProcessRecord lastUsed60MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(60).toMillis(), 1024L, 10000);
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
processList.add(lastUsed60MinutesAgo);
ProcessRecord lastUsed15MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(lastUsed15MinutesAgo);
ProcessRecord lastUsed17MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20);
processList.add(lastUsed17MinutesAgo);
// Only re-ranking 5 entries so this should stay in most recent position.
ProcessRecord lastUsed30MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20);
processList.add(lastUsed30MinutesAgo);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 5 ordered by least recently used first, then last processes position unchanged.
assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo,
lastUsed40MinutesAgo, lastUsed17MinutesAgo, lastUsed15MinutesAgo,
- lastUsed30MinutesAgo);
+ lastUsed30MinutesAgo).inOrder();
}
@Test
public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 0.0f,
/* pssWeight= */ 1.0f,
/* lruWeight= */ 0.0f);
@@ -209,145 +247,459 @@
ProcessList list = new ProcessList();
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(rss10k);
ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(rss20k);
ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(60).toMillis(), 1024L, 10000);
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
processList.add(rss1k);
ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(rss100k);
ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
processList.add(rss2k);
ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
processList.add(rss15k);
// Only re-ranking 6 entries so this should stay in most recent position.
ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 6 ordered by largest pss, then last processes position unchanged.
assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
- rss16k);
+ rss16k).inOrder();
}
@Test
+ public void reRankLruCachedApps_rssImpactsOrdering_cachedRssValues()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 10000000,
+ /* usesWeight= */ 0.0f,
+ /* pssWeight= */ 1.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(rss10k);
+ ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(rss20k);
+ ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
+ processList.add(rss1k);
+ ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(rss100k);
+ ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(rss2k);
+ ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
+ processList.add(rss15k);
+ // Only re-ranking 6 entries so this should stay in most recent position.
+ ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
+ processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // First 6 ordered by largest pss, then last processes position unchanged.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+
+ // Clear mPidToRss so that Process.getRss calls fail.
+ mPidToRss.clear();
+ // Mix up the process list to ensure that CacheOomRanker actually re-ranks.
+ Collections.swap(processList, 0, 1);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // Re ranking is the same.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_rssImpactsOrdering_profileRss()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ false,
+ /* rssUpdateRateMs= */ 10000000,
+ /* usesWeight= */ 0.0f,
+ /* pssWeight= */ 1.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 0L, 1000);
+ rss10k.mProfile.setLastRss(10 * 1024L);
+ processList.add(rss10k);
+ ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 0L, 2000);
+ rss20k.mProfile.setLastRss(20 * 1024L);
+ processList.add(rss20k);
+ ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10000);
+ rss1k.mProfile.setLastRss(1024L);
+ processList.add(rss1k);
+ ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10);
+ rss100k.mProfile.setLastRss(100 * 1024L);
+ processList.add(rss100k);
+ ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 0L, 20);
+ rss2k.mProfile.setLastRss(2 * 1024L);
+ processList.add(rss2k);
+ ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
+ rss15k.mProfile.setLastRss(15 * 1024L);
+ processList.add(rss15k);
+ // Only re-ranking 6 entries so this should stay in most recent position.
+ ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
+ rss16k.mProfile.setLastRss(16 * 1024L);
+ processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ // This should not be used, as RSS values are taken from mProfile.
+ mPidToRss.clear();
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // First 6 ordered by largest pss, then last processes position unchanged.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+
+ // Clear mPidToRss so that Process.getRss calls fail.
+ mPidToRss.clear();
+ // Mix up the process list to ensure that CacheOomRanker actually re-ranks.
+ Collections.swap(processList, 0, 1);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // Re ranking is the same.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+ }
+
+
+ @Test
public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 4,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 1.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- list.setLruProcessServiceStartLSP(1);
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(used1000);
ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(used2000);
ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(used10);
ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
processList.add(used20);
// Only re-ranking 6 entries so last two should stay in most recent position.
ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
processList.add(used500);
ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 4 ordered by uses, then last processes position unchanged.
assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500,
- used200);
+ used200).inOrder();
}
@Test
- public void reRankLruCachedApps_notEnoughProcesses() throws InterruptedException {
+ public void reRankLruCachedApps_fewProcesses() throws InterruptedException {
setConfig(/* numberToReRank= */ 4,
- /* usesWeight= */ 0.5f,
- /* pssWeight= */ 0.2f,
- /* lruWeight= */ 0.3f);
-
- ProcessList list = new ProcessList();
- ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
- ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
- processList.add(unknownAdj1);
- ProcessRecord unknownAdj2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
- processList.add(unknownAdj2);
- ProcessRecord unknownAdj3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
- processList.add(unknownAdj3);
- ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
- processList.add(foregroundAdj);
- ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
- processList.add(serviceAdj);
- ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
- processList.add(systemAdj);
-
- // 6 Processes but only 3 in eligible for cache so no re-ranking.
- mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
-
- // All positions unchanged.
- assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3,
- foregroundAdj, serviceAdj, systemAdj);
- }
-
- @Test
- public void reRankLruCachedApps_notEnoughNonServiceProcesses() throws InterruptedException {
- setConfig(/* numberToReRank= */ 4,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 1.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- list.setLruProcessServiceStartLSP(4);
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(used1000);
ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(used2000);
ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(used10);
- ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
- processList.add(used20);
- ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
- processList.add(used500);
- ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
- processList.add(used200);
+ ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(foregroundAdj);
+ ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(serviceAdj);
+ ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(systemAdj);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
- // All positions unchanged.
- assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
- used200);
+ // 6 processes, only 3 in eligible for cache, so only those are re-ranked.
+ assertThat(processList).containsExactly(used10, used1000, used2000,
+ foregroundAdj, serviceAdj, systemAdj).inOrder();
}
- private void setConfig(int numberToReRank, float useWeight, float pssWeight, float lruWeight)
+ @Test
+ public void reRankLruCachedApps_fewNonServiceProcesses() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 4,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ ProcessRecord service1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(service1);
+ ProcessRecord service2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(service2);
+ ProcessRecord service3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(service3);
+ list.setLruProcessServiceStartLSP(3);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // Services unchanged, rest re-ranked.
+ assertThat(processList).containsExactly(used10, used1000, used2000, service1, service2,
+ service3).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_manyProcessesThenFew() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList set1List = new ProcessList();
+ ArrayList<ProcessRecord> set1ProcessList = set1List.getLruProcessesLSP();
+ ProcessRecord set1Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ set1ProcessList.add(set1Used1000);
+ ProcessRecord set1Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ set1ProcessList.add(set1Used2000);
+ ProcessRecord set1Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ set1ProcessList.add(set1Used10);
+ ProcessRecord set1Uses20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ set1ProcessList.add(set1Uses20);
+ ProcessRecord set1Uses500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ set1ProcessList.add(set1Uses500);
+ ProcessRecord set1Uses200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ set1ProcessList.add(set1Uses200);
+ set1List.setLruProcessServiceStartLSP(set1ProcessList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(set1ProcessList,
+ set1List.getLruProcessServiceStartLOSP());
+ assertThat(set1ProcessList).containsExactly(set1Used10, set1Uses20, set1Uses200,
+ set1Uses500, set1Used1000, set1Used2000).inOrder();
+
+ ProcessList set2List = new ProcessList();
+ ArrayList<ProcessRecord> set2ProcessList = set2List.getLruProcessesLSP();
+ ProcessRecord set2Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ set2ProcessList.add(set2Used1000);
+ ProcessRecord set2Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ set2ProcessList.add(set2Used2000);
+ ProcessRecord set2Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ set2ProcessList.add(set2Used10);
+ ProcessRecord set2ForegroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ set2ProcessList.add(set2ForegroundAdj);
+ ProcessRecord set2ServiceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ set2ProcessList.add(set2ServiceAdj);
+ ProcessRecord set2SystemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ set2ProcessList.add(set2SystemAdj);
+ set2List.setLruProcessServiceStartLSP(set2ProcessList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(set2ProcessList,
+ set2List.getLruProcessServiceStartLOSP());
+ assertThat(set2ProcessList).containsExactly(set2Used10, set2Used1000, set2Used2000,
+ set2ForegroundAdj, set2ServiceAdj, set2SystemAdj).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 3,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ // Preserving the top 3 processes, so these should not be re-ranked.
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // First 3 ordered by uses, then last processes position unchanged.
+ assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500,
+ used200).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps_allAppsUnchanged()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 100,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // Nothing reordered, as we preserve the top 100 apps.
+ assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
+ used200).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps_negativeReplacedWithDefault()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ -100,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ // Negative preserveTopNApps interpreted as the default (3), so the last three are unranked.
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // First 3 apps re-ranked, as preserveTopNApps is interpreted as 3.
+ assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500,
+ used200).inOrder();
+ }
+
+ private void setConfig(int numberToReRank, int preserveTopNApps, boolean useFrequentRss,
+ long rssUpdateRateMs, float usesWeight, float pssWeight, float lruWeight)
throws InterruptedException {
mExecutor.init(4);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -355,6 +707,18 @@
Integer.toString(numberToReRank),
false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS,
+ Integer.toString(preserveTopNApps),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_USE_FREQUENT_RSS,
+ Boolean.toString(useFrequentRss),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS,
+ Long.toString(rssUpdateRateMs),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
Float.toString(lruWeight),
false);
@@ -364,17 +728,19 @@
false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT,
- Float.toString(useWeight),
+ Float.toString(usesWeight),
false);
mExecutor.waitForLatch();
assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank);
+ assertThat(mCacheOomRanker.mUseFrequentRss).isEqualTo(useFrequentRss);
+ assertThat(mCacheOomRanker.mRssUpdateRateMs).isEqualTo(rssUpdateRateMs);
assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight);
- assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(useWeight);
+ assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(usesWeight);
assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight);
}
private ProcessRecord nextProcessRecord(int setAdj, long lastActivityTime, long lastRss,
- int returnedToCacheCount) {
+ int wentToForegroundCount) {
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = "a.package.name" + mNextPackageName++;
ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++);
@@ -382,14 +748,20 @@
app.info.uid = mNextPackageUid++;
// Exact value does not mater, it can be any state for which compaction is allowed.
app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- app.mState.setSetAdj(setAdj);
+ app.mState.setCurAdj(setAdj);
app.setLastActivityTime(lastActivityTime);
- app.mProfile.setLastRss(lastRss);
+ mPidToRss.put(app.getPid(), lastRss);
app.mState.setCached(false);
- for (int i = 0; i < returnedToCacheCount; ++i) {
- app.mState.setCached(false);
- app.mState.setCached(true);
+ for (int i = 0; i < wentToForegroundCount; ++i) {
+ app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ app.mState.setSetProcState(ActivityManager.PROCESS_STATE_CACHED_RECENT);
}
+ // Sets the thread returned by ProcessRecord#getThread, which we use to check whether the
+ // app is currently launching.
+ ProcessStatsService processStatsService = new ProcessStatsService(
+ mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(),
+ "procstats"));
+ app.makeActive(mock(IApplicationThread.class), processStatsService);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 4d86c87..85ef8f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -182,7 +182,14 @@
}
private void mockDeviceConfigPerformance() {
- String configString = "mode=2,downscaleFactor=0.5";
+ String configString = "mode=2,downscaleFactor=0.5,useAngle=false";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configString);
+ }
+
+ // ANGLE will be disabled for most apps, so treat enabling ANGLE as a special case.
+ private void mockDeviceConfigPerformanceEnableAngle() {
+ String configString = "mode=2,downscaleFactor=0.5,useAngle=true";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
}
@@ -200,7 +207,7 @@
}
private void mockDeviceConfigInvalid() {
- String configString = "mode=2,downscaleFactor=0.55";
+ String configString = "";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
}
@@ -212,7 +219,8 @@
}
private void mockGameModeOptInAll() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -224,7 +232,8 @@
}
private void mockGameModeOptInPerformance() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -234,7 +243,8 @@
}
private void mockGameModeOptInBattery() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
@@ -244,7 +254,8 @@
}
private void mockInterventionAllowDownscaleTrue() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, true);
@@ -254,7 +265,8 @@
}
private void mockInterventionAllowDownscaleFalse() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false);
@@ -263,6 +275,27 @@
.thenReturn(applicationInfo);
}
+ private void mockInterventionAllowAngleTrue() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ metaDataBundle.putBoolean(
+ GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, true);
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ }
+
+ private void mockInterventionAllowAngleFalse() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ metaDataBundle.putBoolean(
+ GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, false);
+ applicationInfo.metaData = metaDataBundle;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ }
+
/**
* By default game mode is not supported.
*/
@@ -340,11 +373,12 @@
*/
@Test
public void testSetGameModePermissionDenied() {
+ mockModifyGameModeGranted();
+ mockDeviceConfigAll();
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
// Update the game mode so we can read back something valid.
- mockModifyGameModeGranted();
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
@@ -427,6 +461,19 @@
assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling);
}
+ private void checkAngleEnabled(GameManagerService gameManagerService, int gameMode,
+ boolean angleEnabled) {
+ gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+
+ // Validate GamePackageConfiguration returns the correct value.
+ GameManagerService.GamePackageConfiguration config =
+ gameManagerService.getConfig(mPackageName);
+ assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
+
+ // Validate GameManagerService.getAngleEnabled() returns the correct value.
+ assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
+ }
+
/**
* Phenotype device config exists, but is only propagating the default value.
*/
@@ -592,6 +639,50 @@
}
/**
+ * PERFORMANCE game mode is configured through Phenotype. The app hasn't specified any metadata.
+ */
+ @Test
+ public void testInterventionAllowAngleDefault() throws Exception {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockDeviceConfigPerformance();
+ mockModifyGameModeGranted();
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
+ }
+
+ /**
+ * PERFORMANCE game mode is configured through Phenotype. The app has opted-out of ANGLE.
+ */
+ @Test
+ public void testInterventionAllowAngleFalse() throws Exception {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockDeviceConfigPerformanceEnableAngle();
+ mockInterventionAllowAngleFalse();
+ mockModifyGameModeGranted();
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
+ }
+
+ /**
+ * PERFORMANCE game mode is configured through Phenotype. The app has redundantly specified
+ * the ANGLE metadata default value of "true".
+ */
+ @Test
+ public void testInterventionAllowAngleTrue() throws Exception {
+ mockDeviceConfigPerformanceEnableAngle();
+ mockInterventionAllowAngleTrue();
+
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockModifyGameModeGranted();
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, true);
+ }
+
+ /**
* PERFORMANCE game mode is configured through Phenotype, but the app has also opted into the
* same mode. No interventions for this game mode should be available in this case.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index f703e2e..d0b2eda 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -906,6 +906,21 @@
}
@Test
+ public void testProviderRequest_DelayedRequest_Remove() {
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = new LocationRequest.Builder(60000)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+ mManager.unregisterLocationRequest(listener1);
+
+ mInjector.getAlarmHelper().incrementAlarmTime(60000);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ }
+
+ @Test
public void testProviderRequest_SpamRequesting() {
mProvider.setProviderLocation(createLocation(NAME, mRandom));
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 63996f0..4d6f49e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -90,7 +90,7 @@
}
@Test
- public void testThrottle() {
+ public void testThrottle_stationaryExit() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
mProvider.getController().setRequest(request);
@@ -113,6 +113,29 @@
}
@Test
+ public void testThrottle_idleExit() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+ verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+ verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceIdleHelper().setIdle(false);
+ verify(mDelegate, times(2)).onSetRequest(request);
+ verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+ }
+
+ @Test
public void testThrottle_NoInitialLocation() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
index 7a6110b..f17fa62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
@@ -79,8 +79,8 @@
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
@@ -97,8 +97,8 @@
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
@@ -118,8 +118,8 @@
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, null)
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
@@ -133,6 +133,22 @@
}
}
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendModifiedForUser() {
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+ verify(pms).sendPackageBroadcast(
+ eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+
+ var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(modifiedUids).asList().containsExactly(
+ packageSetting1.appId, packageSetting2.appId)
+ }
+
private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
this.put(TEST_USER_ID, uids)
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
new file mode 100644
index 0000000..f94377f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_VR;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import static com.android.server.power.ScreenUndimDetector.DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
+import static com.android.server.power.ScreenUndimDetector.KEY_KEEP_SCREEN_ON_ENABLED;
+import static com.android.server.power.ScreenUndimDetector.KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
+import static com.android.server.power.ScreenUndimDetector.KEY_UNDIMS_REQUIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.server.power.ScreenUndimDetector}
+ */
+@RunWith(JUnit4.class)
+public class ScreenUndimDetectorTest {
+ private static final List<Integer> ALL_POLICIES =
+ Arrays.asList(POLICY_OFF,
+ POLICY_DOZE,
+ POLICY_DIM,
+ POLICY_BRIGHT,
+ POLICY_VR);
+
+ @ClassRule
+ public static final TestableContext sContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ private ScreenUndimDetector mScreenUndimDetector;
+
+ private final TestClock mClock = new TestClock();
+
+ private static class TestClock extends ScreenUndimDetector.InternalClock {
+ long mCurrentTime = 0;
+ @Override
+ public long getCurrentTime() {
+ return mCurrentTime;
+ }
+
+ public void advanceTime(long millisAdvanced) {
+ mCurrentTime += millisAdvanced;
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(1), false /*makeDefault*/);
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
+ Long.toString(DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS),
+ false /*makeDefault*/);
+
+ mScreenUndimDetector = new ScreenUndimDetector(mClock);
+ mScreenUndimDetector.systemReady(sContext);
+ }
+
+ @Test
+ public void recordScreenPolicy_disabledByFlag_noop() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/);
+
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+
+ @Test
+ public void recordScreenPolicy_samePolicy_noop() {
+ for (int policy : ALL_POLICIES) {
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(policy);
+ mScreenUndimDetector.recordScreenPolicy(policy);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_extends() {
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isTrue();
+ }
+
+ @Test
+ public void recordScreenPolicy_otherTransitions_doesNotExtend() {
+ for (int from : ALL_POLICIES) {
+ for (int to : ALL_POLICIES) {
+ if (from == POLICY_DIM && to == POLICY_BRIGHT) {
+ continue;
+ }
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(from);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_twoUndimsNeeded_extends() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isTrue();
+ }
+
+ @Test
+ public void recordScreenPolicy_dimBrightDimOff_resetsCounter_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+
+ @Test
+ public void recordScreenPolicy_undimToOff_resetsCounter() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+
+ @Test
+ public void recordScreenPolicy_undimOffUndim_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ // undim
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ // off
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+ // second undim
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(1);
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_tooFarApart_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ mClock.advanceTime(DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS + 5);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(1);
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToNonBright_resets() {
+ for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+ setup();
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis = 123;
+ mScreenUndimDetector.mWakeLock.acquire();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+
+ }
+
+ @Test
+ public void recordScreenPolicy_brightToNonDim_resets() {
+ for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+ setup();
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis = 123;
+ mScreenUndimDetector.mWakeLock.acquire();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_otherTransitions_doesNotReset() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(3),
+ false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ for (int from : ALL_POLICIES) {
+ for (int to : ALL_POLICIES) {
+ if (from == POLICY_DIM && to != POLICY_BRIGHT) {
+ continue;
+ }
+ if (from == POLICY_BRIGHT && to != POLICY_DIM) {
+ continue;
+ }
+ mScreenUndimDetector.mCurrentScreenPolicy = POLICY_OFF;
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis =
+ SystemClock.currentThreadTimeMillis();
+
+ mScreenUndimDetector.recordScreenPolicy(from);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isNotEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isNotEqualTo(0);
+ }
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
index ba79a76..38f01b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -16,12 +16,18 @@
package com.android.server.sensorprivacy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
+import android.app.AppOpsManagerInternal;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Environment;
@@ -33,8 +39,10 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
import com.android.server.SensorPrivacyService;
+import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -44,6 +52,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.concurrent.CompletableFuture;
@RunWith(AndroidTestingRunner.class)
public class SensorPrivacyServiceMockingTest {
@@ -63,10 +72,21 @@
public static final String PERSISTENCE_FILE6 =
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
+ public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
+ public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml";
+ public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml";
+ public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
+
private Context mContext;
@Mock
private AppOpsManager mMockedAppOpsManager;
@Mock
+ private AppOpsManagerInternal mMockedAppOpsManagerInternal;
+ @Mock
private UserManagerInternal mMockedUserManagerInternal;
@Mock
private ActivityManager mMockedActivityManager;
@@ -134,13 +154,103 @@
}
}
+ @Test
+ public void testServiceInit_AppOpsRestricted_micMute_camMute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE, true, true);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micMute_camUnmute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE, true, false);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micUnmute_camMute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE, false, true);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micUnmute_camUnmute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE, false, false);
+ }
+
+ private void testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute,
+ boolean expectedMicState, boolean expectedCamState)
+ throws IOException {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(LocalServices.class)
+ .spyStatic(Environment.class)
+ .startMocking();
+
+ try {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ spyOn(mContext);
+
+ doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+ doReturn(mMockedAppOpsManagerInternal)
+ .when(() -> LocalServices.getService(AppOpsManagerInternal.class));
+ doReturn(mMockedUserManagerInternal)
+ .when(() -> LocalServices.getService(UserManagerInternal.class));
+ doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
+ doReturn(mMockedActivityTaskManager)
+ .when(mContext).getSystemService(ActivityTaskManager.class);
+ doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
+ TelephonyManager.class);
+
+ String dataDir = mContext.getApplicationInfo().dataDir;
+ doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
+
+ File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
+ onDeviceFile.delete();
+
+ doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
+ doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+ .getUserInfo(0);
+
+ CompletableFuture<Boolean> micState = new CompletableFuture<>();
+ CompletableFuture<Boolean> camState = new CompletableFuture<>();
+ doAnswer(invocation -> {
+ int code = invocation.getArgument(0);
+ boolean restricted = invocation.getArgument(1);
+ if (code == AppOpsManager.OP_RECORD_AUDIO) {
+ micState.complete(restricted);
+ } else if (code == AppOpsManager.OP_CAMERA) {
+ camState.complete(restricted);
+ }
+ return null;
+ }).when(mMockedAppOpsManagerInternal).setGlobalRestriction(anyInt(), anyBoolean(),
+ any());
+
+ initServiceWithPersistenceFile(onDeviceFile, persistenceFileMicMuteCamMute, 0);
+
+ Assert.assertTrue(micState.join() == expectedMicState);
+ Assert.assertTrue(camState.join() == expectedCamState);
+
+ } finally {
+ mockitoSession.finishMocking();
+ }
+ }
+
private void initServiceWithPersistenceFile(File onDeviceFile,
String persistenceFilePath) throws IOException {
+ initServiceWithPersistenceFile(onDeviceFile, persistenceFilePath, -1);
+ }
+
+ private void initServiceWithPersistenceFile(File onDeviceFile,
+ String persistenceFilePath, int startingUserId) throws IOException {
if (persistenceFilePath != null) {
Files.copy(mContext.getAssets().open(persistenceFilePath),
onDeviceFile.toPath());
}
- new SensorPrivacyService(mContext);
+ SensorPrivacyService service = new SensorPrivacyService(mContext);
+ if (startingUserId != -1) {
+ SystemService.TargetUser mockedTargetUser =
+ ExtendedMockito.mock(SystemService.TargetUser.class);
+ doReturn(startingUserId).when(mockedTargetUser).getUserIdentifier();
+ service.onUserStarting(mockedTargetUser);
+ }
onDeviceFile.delete();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 53483f6..a49afc7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -18,13 +18,18 @@
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.os.FileObserver.CLOSE_WRITE;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_CROP;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
@@ -34,6 +39,7 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
@@ -41,6 +47,7 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Context;
@@ -49,8 +56,10 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
+import android.graphics.Color;
import android.hardware.display.DisplayManager;
-import android.os.UserHandle;
+import android.os.RemoteException;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
@@ -143,7 +152,6 @@
sContext.getTestablePermissions().setPermission(
android.Manifest.permission.SET_WALLPAPER,
PackageManager.PERMISSION_GRANTED);
- doNothing().when(sContext).sendBroadcastAsUser(any(), any());
//Wallpaper components
sWallpaperService = mock(IWallpaperConnection.Stub.class);
@@ -180,6 +188,7 @@
MockitoAnnotations.initMocks(this);
sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+ doNothing().when(sContext).sendBroadcastAsUser(any(), any());
final Display mockDisplay = mock(Display.class);
doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
@@ -242,13 +251,13 @@
*/
@Test
public void testDataCorrectAfterBoot() {
- mService.switchUser(UserHandle.USER_SYSTEM, null);
+ mService.switchUser(USER_SYSTEM, null);
final WallpaperData fallbackData = mService.mFallbackWallpaper;
assertEquals("Fallback wallpaper component should be ImageWallpaper.",
sImageWallpaperComponentName, fallbackData.wallpaperComponent);
- verifyLastWallpaperData(UserHandle.USER_SYSTEM, sDefaultWallpaperComponent);
+ verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent);
verifyDisplayData();
}
@@ -261,7 +270,7 @@
assumeThat(sDefaultWallpaperComponent,
not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
- final int testUserId = UserHandle.USER_SYSTEM;
+ final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
@@ -281,7 +290,7 @@
*/
@Test
public void testSetCurrentComponent() throws Exception {
- final int testUserId = UserHandle.USER_SYSTEM;
+ final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
@@ -387,6 +396,42 @@
assertEquals(systemWallpaperData.primaryColors, shouldMatchSystem.primaryColors);
}
+ @Test
+ public void testWallpaperManagerCallbackInRightOrder() throws RemoteException {
+ WallpaperData wallpaper = new WallpaperData(
+ USER_SYSTEM, mService.getWallpaperDir(USER_SYSTEM), WALLPAPER, WALLPAPER_CROP);
+ wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ spyOn(wallpaper);
+ doReturn(wallpaper).when(mService).getWallpaperSafeLocked(wallpaper.userId, FLAG_SYSTEM);
+ doNothing().when(mService).switchWallpaper(any(), any());
+ doReturn(true).when(mService)
+ .bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
+ doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
+ doNothing().when(mService).generateCrop(wallpaper);
+
+ // timestamps of {ACTION_WALLPAPER_CHANGED, onWallpaperColorsChanged}
+ final long[] timestamps = new long[2];
+ doAnswer(invocation -> timestamps[0] = SystemClock.elapsedRealtime())
+ .when(sContext).sendBroadcastAsUser(any(), any());
+ doAnswer(invocation -> timestamps[1] = SystemClock.elapsedRealtime())
+ .when(mService).notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
+
+ assertNull(wallpaper.wallpaperObserver);
+ mService.switchUser(wallpaper.userId, null);
+ assertNotNull(wallpaper.wallpaperObserver);
+ // We will call onEvent directly, so stop watching the file.
+ wallpaper.wallpaperObserver.stopWatching();
+
+ spyOn(wallpaper.wallpaperObserver);
+ doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false);
+ wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER);
+
+ // ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged.
+ assertTrue(timestamps[1] > timestamps[0]);
+ }
+
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
private void verifyNoConnectionBeforeLastUser(int lastUserId) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 2892bf5..b3f7587 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -74,6 +74,7 @@
public class AuthSessionTest {
private static final String TEST_PACKAGE = "test_package";
+ private static final long TEST_REQUEST_ID = 22;
@Mock private Context mContext;
@Mock private ITrustManager mTrustManager;
@@ -112,6 +113,7 @@
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
0 /* operationId */,
0 /* userId */);
@@ -133,6 +135,7 @@
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
operationId,
userId);
assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size());
@@ -153,6 +156,7 @@
eq(userId),
eq(mSensorReceiver),
eq(TEST_PACKAGE),
+ eq(TEST_REQUEST_ID),
eq(sensor.getCookie()),
anyBoolean() /* allowBackgroundAuthentication */);
}
@@ -185,6 +189,33 @@
}
@Test
+ public void testCancelReducesAppetiteForCookies() throws Exception {
+ setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
+ setupFingerprint(1 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 44 /* operationId */,
+ 2 /* userId */);
+
+ session.goToInitialState();
+
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+ }
+
+ session.onCancelAuthSession(false /* force */);
+
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ session.onCookieReceived(sensor.getCookie());
+ assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+ }
+ }
+
+ @Test
public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
@@ -212,6 +243,7 @@
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
operationId,
userId);
assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size());
@@ -238,7 +270,7 @@
// fingerprint sensor does not start even if all cookies are received
assertEquals(STATE_AUTH_STARTED, session.getState());
verify(mStatusBarService).showAuthenticationDialog(any(), any(), any(),
- anyBoolean(), anyBoolean(), anyInt(), any(), anyLong(), anyInt());
+ anyBoolean(), anyBoolean(), anyInt(), anyLong(), any(), anyLong(), anyInt());
// Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
session.onDialogAnimatedIn();
@@ -277,6 +309,7 @@
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
0 /* operationId */,
0 /* userId */);
@@ -285,7 +318,8 @@
sessionConsumer.accept(session);
- verify(faceAuthenticator).cancelAuthenticationFromService(eq(mToken), eq(TEST_PACKAGE));
+ verify(faceAuthenticator).cancelAuthenticationFromService(
+ eq(mToken), eq(TEST_PACKAGE), eq(TEST_REQUEST_ID));
}
private PreAuthInfo createPreAuthInfo(List<BiometricSensor> sensors, int userId,
@@ -302,14 +336,14 @@
private AuthSession createAuthSession(List<BiometricSensor> sensors,
boolean checkDevicePolicyManager, @Authenticators.Types int authenticators,
- long operationId, int userId) throws RemoteException {
+ long requestId, long operationId, int userId) throws RemoteException {
final PromptInfo promptInfo = createPromptInfo(authenticators);
final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, promptInfo,
checkDevicePolicyManager);
return new AuthSession(mContext, mStatusBarService, mSysuiReceiver, mKeyStore,
- mRandom, mClientDeathReceiver, preAuthInfo, mToken, operationId, userId,
+ mRandom, mClientDeathReceiver, preAuthInfo, mToken, requestId, operationId, userId,
mSensorReceiver, mClientReceiver, TEST_PACKAGE, promptInfo,
false /* debugEnabled */, mFingerprintSensorProps);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 7c7afb7..69d8e89 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -85,6 +85,7 @@
import org.mockito.MockitoAnnotations;
import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
@Presubmit
@SmallTest
@@ -93,6 +94,7 @@
private static final String TAG = "BiometricServiceTest";
private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final long TEST_REQUEST_ID = 44;
private static final String ERROR_HW_UNAVAILABLE = "hw_unavailable";
private static final String ERROR_NOT_RECOGNIZED = "not_recognized";
@@ -151,6 +153,7 @@
.thenReturn(mock(BiometricStrengthController.class));
when(mInjector.getTrustManager()).thenReturn(mTrustManager);
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
+ when(mInjector.getRequestGenerator()).thenReturn(new AtomicLong(TEST_REQUEST_ID - 1));
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -215,8 +218,7 @@
mBiometricService.mCurrentAuthSession.getState());
verify(mBiometricService.mCurrentAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
- .cancelAuthenticationFromService(any(),
- any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
// Simulate ERROR_CANCELED received from HAL
mBiometricService.mBiometricSensorReceiver.onError(
@@ -272,8 +274,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -357,8 +360,9 @@
eq(false) /* credentialAllowed */,
eq(false) /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -467,6 +471,7 @@
anyInt() /* userId */,
any(IBiometricSensorReceiver.class),
anyString() /* opPackageName */,
+ eq(TEST_REQUEST_ID),
cookieCaptor.capture() /* cookie */,
anyBoolean() /* allowBackgroundAuthentication */);
@@ -488,8 +493,9 @@
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Hardware authenticated
@@ -543,8 +549,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -705,8 +712,9 @@
anyBoolean() /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
anyString(),
- anyLong() /* sessionId */,
+ anyLong() /* requestId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -805,8 +813,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -885,8 +894,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -1030,8 +1040,7 @@
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mSensors.get(0).impl).cancelAuthenticationFromService(
- any(),
- any());
+ any(), any(), anyLong());
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -1051,7 +1060,7 @@
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
}
@Test
@@ -1071,7 +1080,7 @@
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
}
@Test
@@ -1088,7 +1097,7 @@
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
@@ -1126,7 +1135,7 @@
false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
- TEST_PACKAGE_NAME);
+ TEST_PACKAGE_NAME, TEST_REQUEST_ID);
waitForIdle();
// Pretend that the HAL has responded to cancel with ERROR_CANCELED
@@ -1353,8 +1362,8 @@
int authenticators = Authenticators.BIOMETRIC_STRONG;
assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- authenticators);
+ long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
@@ -1366,7 +1375,7 @@
authenticators = Authenticators.BIOMETRIC_WEAK;
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
authenticators);
waitForIdle();
@@ -1377,8 +1386,9 @@
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Requesting strong and credential, when credential is setup
@@ -1387,7 +1397,7 @@
when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
authenticators);
waitForIdle();
@@ -1399,8 +1409,9 @@
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Un-downgrading the authenticator allows successful strong auth
@@ -1414,7 +1425,7 @@
authenticators = Authenticators.BIOMETRIC_STRONG;
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -1424,8 +1435,9 @@
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -1617,11 +1629,12 @@
mBiometricService.mStatusBarService = mock(IStatusBarService.class);
}
- private void invokeAuthenticateAndStart(IBiometricService.Stub service,
+ private long invokeAuthenticateAndStart(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
Integer authenticators) throws Exception {
// Request auth, creates a pending session
- invokeAuthenticate(service, receiver, requireConfirmation, authenticators);
+ final long requestId = invokeAuthenticate(
+ service, receiver, requireConfirmation, authenticators);
waitForIdle();
startPendingAuthSession(mBiometricService);
@@ -1629,6 +1642,8 @@
assertNotNull(mBiometricService.mCurrentAuthSession);
assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+
+ return requestId;
}
private static void startPendingAuthSession(BiometricService service) throws Exception {
@@ -1644,10 +1659,10 @@
service.mImpl.onReadyForAuthentication(cookie);
}
- private static void invokeAuthenticate(IBiometricService.Stub service,
+ private static long invokeAuthenticate(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
Integer authenticators) throws Exception {
- service.authenticate(
+ return service.authenticate(
new Binder() /* token */,
0 /* operationId */,
0 /* userId */,
@@ -1657,9 +1672,9 @@
false /* checkDevicePolicy */));
}
- private static void invokeAuthenticateForWorkApp(IBiometricService.Stub service,
+ private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, Integer authenticators) throws Exception {
- service.authenticate(
+ return service.authenticate(
new Binder() /* token */,
0 /* operationId */,
0 /* userId */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index f4d1499..e3e3900 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -40,6 +40,7 @@
import android.testing.TestableContext;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -193,7 +194,7 @@
// Request it to be canceled. The operation can be canceled immediately, and the scheduler
// should go back to idle, since in this case the framework has not even requested the HAL
// to authenticate yet.
- mScheduler.cancelAuthenticationOrDetection(mToken);
+ mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
assertNull(mScheduler.mCurrentOperation);
}
@@ -303,7 +304,7 @@
mScheduler.mPendingOperations.getFirst().mState);
// Request cancel before the authentication client has started
- mScheduler.cancelAuthenticationOrDetection(mToken);
+ mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
waitForIdle();
assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
mScheduler.mPendingOperations.getFirst().mState);
@@ -318,6 +319,107 @@
}
@Test
+ public void testCancels_whenAuthRequestIdNotSet() {
+ testCancelsWhenRequestId(null /* requestId */, 2, true /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdNotSet_notStarted() {
+ testCancelsWhenRequestId(null /* requestId */, 2, false /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdMatches() {
+ testCancelsWhenRequestId(200L, 200, true /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdMatches_noStarted() {
+ testCancelsWhenRequestId(200L, 200, false /* started */);
+ }
+
+ @Test
+ public void testDoesNotCancel_whenAuthRequestIdMismatched() {
+ testCancelsWhenRequestId(10L, 20, true /* started */);
+ }
+
+ @Test
+ public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
+ testCancelsWhenRequestId(10L, 20, false /* started */);
+ }
+
+ private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
+ boolean started) {
+ final boolean matches = requestId == null || requestId == cancelRequestId;
+ final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ if (requestId != null) {
+ client.setRequestId(requestId);
+ }
+
+ mScheduler.scheduleClientMonitor(client);
+ if (started) {
+ mScheduler.startPreparedClient(client.getCookie());
+ }
+ waitForIdle();
+ mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
+ waitForIdle();
+
+ assertEquals(matches && started ? 1 : 0, client.mNumCancels);
+
+ if (matches) {
+ if (started) {
+ assertEquals(Operation.STATE_STARTED_CANCELING,
+ mScheduler.mCurrentOperation.mState);
+ }
+ } else {
+ if (started) {
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ } else {
+ assertEquals(Operation.STATE_WAITING_FOR_COOKIE,
+ mScheduler.mCurrentOperation.mState);
+ }
+ }
+ }
+
+ @Test
+ public void testCancelsPending_whenAuthRequestIdsSet() {
+ final long requestId1 = 10;
+ final long requestId2 = 20;
+ final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client1 = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ client1.setRequestId(requestId1);
+ final TestAuthenticationClient client2 = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ client2.setRequestId(requestId2);
+
+ mScheduler.scheduleClientMonitor(client1);
+ mScheduler.scheduleClientMonitor(client2);
+ mScheduler.startPreparedClient(client1.getCookie());
+ waitForIdle();
+ mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
+ waitForIdle();
+
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ assertEquals(Operation.STATE_WAITING_IN_QUEUE,
+ mScheduler.mPendingOperations.getFirst().mState);
+
+ mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
+ waitForIdle();
+
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
+ mScheduler.mPendingOperations.getFirst().mState);
+ }
+
+ @Test
public void testInterruptPrecedingClients_whenExpected() {
final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
withSettings().extraInterfaces(Interruptable.class));
@@ -348,6 +450,17 @@
verify((Interruptable) interruptableMonitor, never()).cancel();
}
+ @Test
+ public void testClientDestroyed_afterFinish() {
+ final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
+ final TestClientMonitor client =
+ new TestClientMonitor(mContext, mToken, nonNullDaemon);
+ mScheduler.scheduleClientMonitor(client);
+ client.mCallback.onClientFinished(client, true /* success */);
+ waitForIdle();
+ assertTrue(client.wasDestroyed());
+ }
+
private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
}
@@ -366,12 +479,10 @@
@Override
protected void stopHalOperation() {
-
}
@Override
protected void startHalOperation() {
-
}
@Override
@@ -386,6 +497,7 @@
}
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+ int mNumCancels = 0;
public TestAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -417,6 +529,11 @@
public boolean wasUserDetected() {
return false;
}
+
+ public void cancel() {
+ mNumCancels++;
+ super.cancel();
+ }
}
private static class TestClientMonitor2 extends TestClientMonitor {
@@ -437,6 +554,7 @@
private static class TestClientMonitor extends HalClientMonitor<Object> {
private boolean mUnableToStart;
private boolean mStarted;
+ private boolean mDestroyed;
public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
@NonNull LazyDaemon<Object> lazyDaemon) {
@@ -475,6 +593,11 @@
}
+ @Override
+ public void destroy() {
+ mDestroyed = true;
+ }
+
public boolean wasUnableToStart() {
return mUnableToStart;
}
@@ -482,6 +605,11 @@
public boolean hasStarted() {
return mStarted;
}
+
+ public boolean wasDestroyed() {
+ return mDestroyed;
+ }
+
}
private static void waitForIdle() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
new file mode 100644
index 0000000..09b5c5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class CompositeCallbackTest {
+
+ @Test
+ public void testNullCallback() {
+ BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
+ BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
+ BaseClientMonitor.Callback callback3 = null;
+
+ BaseClientMonitor.CompositeCallback callback = new BaseClientMonitor.CompositeCallback(
+ callback1, callback2, callback3);
+
+ BaseClientMonitor clientMonitor = mock(BaseClientMonitor.class);
+
+ callback.onClientStarted(clientMonitor);
+ verify(callback1).onClientStarted(eq(clientMonitor));
+ verify(callback2).onClientStarted(eq(clientMonitor));
+
+ callback.onClientFinished(clientMonitor, true /* success */);
+ verify(callback1).onClientFinished(eq(clientMonitor), eq(true));
+ verify(callback2).onClientFinished(eq(clientMonitor), eq(true));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
new file mode 100644
index 0000000..38e8dfa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.fingerprint.FingerprintStateListener;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.EnrollClient;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class FingerprintStateCallbackTest {
+
+ private FingerprintStateCallback mCallback;
+
+ @Mock
+ FingerprintStateListener mFingerprintStateListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mCallback = new FingerprintStateCallback();
+ mCallback.registerFingerprintStateListener(mFingerprintStateListener);
+ }
+
+ @Test
+ public void testNoEnrollmentsToEnrollments_callbackNotified() {
+ testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
+ true /* expectCallback */, true /* expectedCallbackValue */);
+ }
+
+ @Test
+ public void testEnrollmentsToNoEnrollments_callbackNotified() {
+ testEnrollmentCallback(true /* changed */, false /* isNowEnrolled */,
+ true /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
+ @Test
+ public void testEnrollmentsToEnrollments_callbackNotNotified() {
+ testEnrollmentCallback(false /* changed */, true /* isNowEnrolled */,
+ false /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
+ private void testEnrollmentCallback(boolean changed, boolean isNowEnrolled,
+ boolean expectCallback, boolean expectedCallbackValue) {
+ EnrollClient<?> client = mock(EnrollClient.class);
+
+ final int userId = 10;
+ final int sensorId = 100;
+
+ when(client.hasEnrollmentStateChanged()).thenReturn(changed);
+ when(client.hasEnrollments()).thenReturn(isNowEnrolled);
+ when(client.getTargetUserId()).thenReturn(userId);
+ when(client.getSensorId()).thenReturn(sensorId);
+
+ mCallback.onClientFinished(client, true /* success */);
+ if (expectCallback) {
+ verify(mFingerprintStateListener).onEnrollmentsChanged(eq(userId), eq(sensorId),
+ eq(expectedCallbackValue));
+ } else {
+ verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+ anyBoolean());
+ }
+ }
+
+ @Test
+ public void testAuthentication_enrollmentCallbackNeverNotified() {
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ mCallback.onClientFinished(client, true /* success */);
+ verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+ anyBoolean());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 35c37ef..b51918e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -37,6 +37,7 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
@@ -58,6 +59,8 @@
private UserManager mUserManager;
@Mock
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @Mock
+ private FingerprintStateCallback mFingerprintStateCallback;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -87,8 +90,8 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ mFingerprintProvider = new TestableFingerprintProvider(mContext, mFingerprintStateCallback,
+ mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
@SuppressWarnings("rawtypes")
@@ -133,11 +136,12 @@
private static class TestableFingerprintProvider extends FingerprintProvider {
public TestableFingerprintProvider(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- super(context, props, halInstanceName, lockoutResetDispatcher,
+ super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
gestureAvailabilityDispatcher);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index 0a0dcc9..f6b9209 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -41,6 +41,7 @@
import com.android.internal.R;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import org.junit.Before;
import org.junit.Test;
@@ -67,6 +68,8 @@
Fingerprint21.HalResultController mHalResultController;
@Mock
private BiometricScheduler mScheduler;
+ @Mock
+ private FingerprintStateCallback mFingerprintStateCallback;
private LockoutResetDispatcher mLockoutResetDispatcher;
private Fingerprint21 mFingerprint21;
@@ -96,8 +99,9 @@
componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
resetLockoutRequiresHardwareAuthToken);
- mFingerprint21 = new TestableFingerprint21(mContext, sensorProps, mScheduler,
- new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController);
+ mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
+ mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
+ mHalResultController);
}
@Test
@@ -118,11 +122,13 @@
private static class TestableFingerprint21 extends Fingerprint21 {
TestableFingerprint21(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
- super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+ super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 7b20bf0..3ac30d02 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2960,9 +2960,6 @@
assertThat(intent.getAction()).isEqualTo(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, -1))
.isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID));
- assertThat(
- (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN))
- .isEqualTo(admin1);
assertThat(intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION))
.isEqualTo(UserManager.DISALLOW_ADJUST_VOLUME);
@@ -2999,7 +2996,7 @@
assertThat(intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION))
.isEqualTo(DevicePolicyManager.POLICY_DISABLE_CAMERA);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, -1))
- .isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID));
+ .isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_UID));
// ScreenCapture should not be disabled by device owner
intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
assertThat(intent).isNull();
@@ -7753,6 +7750,12 @@
DpmMockContext.CALLER_SYSTEM_USER_UID, admin1.getPackageName(), MODE_DEFAULT);
}
+ @Test
+ public void testGetOrganizationNameForUser_calledByNonPrivilegedApp_throwsException() {
+ assertExpectException(SecurityException.class, "Calling identity is not authorized",
+ () -> dpm.getOrganizationNameForUser(UserHandle.USER_SYSTEM));
+ }
+
private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
userVpnUid, List.of(new AppOpsManager.OpEntry(
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 1ac28ab..418831f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -20,10 +20,13 @@
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
import static com.google.common.truth.Truth.assertThat;
@@ -56,7 +59,11 @@
import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
import android.os.Looper;
+import android.os.RemoteException;
+import android.os.Temperature;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
@@ -68,6 +75,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
@@ -104,6 +112,7 @@
private static final boolean DEBUG = false;
private static final float FLOAT_TOLERANCE = 0.01f;
private static final int DISPLAY_ID = 0;
+ private static final float TRANSITION_POINT = 0.763f;
private Context mContext;
private FakesInjector mInjector;
@@ -116,6 +125,8 @@
public SensorManagerInternal mSensorManagerInternalMock;
@Mock
public DisplayManagerInternal mDisplayManagerInternalMock;
+ @Mock
+ public IThermalService mThermalServiceMock;
@Before
public void setUp() throws Exception {
@@ -124,6 +135,7 @@
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
when(mContext.getContentResolver()).thenReturn(resolver);
mInjector = spy(new FakesInjector());
+ when(mInjector.getThermalService()).thenReturn(mThermalServiceMock);
mHandler = new Handler(Looper.getMainLooper());
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
@@ -742,19 +754,27 @@
director.start(sensorManager);
- ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor<DisplayListener> displayListenerCaptor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ DisplayListener displayListener = displayListenerCaptor.getValue();
+
+ ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
.registerListener(
- listenerCaptor.capture(),
+ sensorListenerCaptor.capture(),
eq(lightSensor),
anyInt(),
any(Handler.class));
- SensorEventListener listener = listenerCaptor.getValue();
+ SensorEventListener sensorListener = sensorListenerCaptor.getValue();
- setBrightness(10);
+ setBrightness(10, 10, displayListener);
// Sensor reads 20 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertVoteForRefreshRate(vote, 90 /*fps*/);
@@ -762,9 +782,11 @@
assertThat(vote).isNotNull();
assertThat(vote.disableRefreshRateSwitching).isTrue();
- setBrightness(125);
+ // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+ // parameter to the necessary threshold
+ setBrightness(10, 125, displayListener);
// Sensor reads 1000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertThat(vote).isNull();
@@ -790,6 +812,14 @@
director.start(sensorManager);
+ ArgumentCaptor<DisplayListener> displayListenerCaptor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ DisplayListener displayListener = displayListenerCaptor.getValue();
+
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
@@ -798,20 +828,22 @@
eq(lightSensor),
anyInt(),
any(Handler.class));
- SensorEventListener listener = listenerCaptor.getValue();
+ SensorEventListener sensorListener = listenerCaptor.getValue();
- setBrightness(100);
+ setBrightness(100, 100, displayListener);
// Sensor reads 2000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertThat(vote).isNull();
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
assertThat(vote).isNull();
- setBrightness(255);
+ // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+ // parameter to the necessary threshold
+ setBrightness(100, 255, displayListener);
// Sensor reads 9000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertVoteForRefreshRate(vote, 60 /*fps*/);
@@ -1401,6 +1433,12 @@
public void testHbmVoting_forHdr() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+ final int hbmRefreshRate = 72;
+
+ // Specify limitation before starting DisplayModeDirector to avoid waiting on property
+ // propagation
+ mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hbmRefreshRate);
+
director.start(createMockSensorManager());
ArgumentCaptor<DisplayListener> captor =
@@ -1420,19 +1458,99 @@
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
- // Turn on HBM
+ // Turn on HBM, with brightness in the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR));
+ new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
- assertVoteForRefreshRate(vote, 60.f);
+ assertVoteForRefreshRate(vote, hbmRefreshRate);
- // Turn off HBM
+ // Turn on HBM, with brightness below the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HBM, with brightness in the HBM range
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, hbmRefreshRate);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HBM, with brightness below the HBM range
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+ }
+
+ @Test
+ public void testHbmObserverGetsUpdatedRefreshRateInHbmSunlight() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+
+ final int initialRefreshRate = 60;
+ mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(initialRefreshRate);
+ director.start(createMockSensorManager());
+ assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+ .isEqualTo(initialRefreshRate);
+
+ final int updatedRefreshRate = 90;
+ mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
+ // Need to wait for the property change to propagate to the main thread.
+ waitForIdleSync();
+ assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+ .isEqualTo(updatedRefreshRate);
+ }
+
+ @Test
+ public void testHbmObserverGetsUpdatedRefreshRateInHbmHdr() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+
+ final int initialRefreshRate = 60;
+ mInjector.getDeviceConfig().setRefreshRateInHbmHdr(initialRefreshRate);
+ director.start(createMockSensorManager());
+ assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
+ .isEqualTo(initialRefreshRate);
+
+ final int updatedRefreshRate = 90;
+ mInjector.getDeviceConfig().setRefreshRateInHbmHdr(updatedRefreshRate);
+ // Need to wait for the property change to propagate to the main thread.
+ waitForIdleSync();
+ assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
+ .isEqualTo(updatedRefreshRate);
}
@Test
@@ -1448,11 +1566,12 @@
| DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
DisplayListener listener = captor.getValue();
+ final int initialRefreshRate = 60;
// Specify Limitation
when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
List.of(new RefreshRateLimitation(
DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
- 60.f, 60.f)));
+ initialRefreshRate, initialRefreshRate)));
// Verify that there is no HBM vote initially
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
@@ -1460,14 +1579,50 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
- assertVoteForRefreshRate(vote, 60.f);
+ assertVoteForRefreshRate(vote, initialRefreshRate);
+
+ // Change refresh rate vote value through DeviceConfig, ensure it takes precedence
+ final int updatedRefreshRate = 90;
+ mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
+ // Need to wait for the property change to propagate to the main thread.
+ waitForIdleSync();
+ assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+ .isEqualTo(updatedRefreshRate);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, updatedRefreshRate);
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn HBM on again and ensure the updated vote value stuck
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, updatedRefreshRate);
+
+ // Reset DeviceConfig refresh rate, ensure vote falls back to the initial value
+ mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(0);
+ // Need to wait for the property change to propagate to the main thread.
+ waitForIdleSync();
+ assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()).isEqualTo(0);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, initialRefreshRate);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1498,20 +1653,145 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
}
@Test
+ public void testHbmVoting_HbmUnsupported() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+ director.start(createMockSensorManager());
+
+ ArgumentCaptor<DisplayListener> captor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ DisplayListener listener = captor.getValue();
+
+ // Specify Limitation
+ when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
+ List.of(new RefreshRateLimitation(
+ DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+ 60.0f, 60.0f)));
+
+ // Verify that there is no HBM vote initially
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HBM when HBM is supported; expect a valid transition point and a vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, 60.0f);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on Sunlight HBM when HBM is unsupported; expect an invalid transition point and
+ // no vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ HBM_TRANSITION_POINT_INVALID));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HDR HBM when HBM is unsupported; expect an invalid transition point and
+ // no vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
+ HBM_TRANSITION_POINT_INVALID));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+ }
+
+ private void setHbmAndAssertRefreshRate(
+ DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
+ when(mInjector.getBrightnessInfo(DISPLAY_ID))
+ .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+
+ final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ if (Float.isNaN(rr)) {
+ assertNull(vote);
+ } else {
+ assertVoteForRefreshRate(vote, rr);
+ }
+ }
+
+ @Test
+ public void testHbmVoting_forSunlightAndHdr() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+
+ // Specify HDR limitation before starting DisplayModeDirector to avoid waiting on property
+ // propagation
+ final int hdrRr = 60;
+ mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hdrRr);
+ director.start(createMockSensorManager());
+
+ ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ DisplayListener listener = captor.getValue();
+
+ // Specify Sunlight limitations
+ final float sunlightRr = 90.0f;
+ when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID))
+ .thenReturn(List.of(new RefreshRateLimitation(
+ DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, sunlightRr,
+ sunlightRr)));
+
+ // Verify that there is no HBM vote initially
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Verify all state transitions
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
+ }
+
+ @Test
public void testHbmVoting_RemovedDisplay() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
@@ -1536,7 +1816,8 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, 60.f);
@@ -1547,12 +1828,52 @@
assertNull(vote);
}
+ @Test
+ public void testSkinTemperature() throws RemoteException {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+ director.start(createMockSensorManager());
+
+ ArgumentCaptor<IThermalEventListener> thermalEventListener =
+ ArgumentCaptor.forClass(IThermalEventListener.class);
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = thermalEventListener.getValue();
+
+ // Verify that there is no skin temperature vote initially.
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+ assertNull(vote);
+
+ // Set the skin temperature to critical and verify that we added a vote.
+ listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+ assertVoteForRefreshRateRange(vote, 0f, 60.f);
+
+ // Set the skin temperature to severe and verify that the vote is gone.
+ listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+ assertNull(vote);
+ }
+
+ private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
+ return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+ }
+
private void assertVoteForRefreshRate(Vote vote, float refreshRate) {
assertThat(vote).isNotNull();
final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate);
assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
}
+ private void assertVoteForRefreshRateRange(
+ Vote vote, float refreshRateLow, float refreshRateHigh) {
+ assertThat(vote).isNotNull();
+ final RefreshRateRange expectedRange =
+ new RefreshRateRange(refreshRateLow, refreshRateHigh);
+ assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
+ }
+
public static class FakeDeviceConfig extends FakeDeviceConfigInterface {
@Override
public String getProperty(String namespace, String name) {
@@ -1575,6 +1896,16 @@
String.valueOf(fps));
}
+ void setRefreshRateInHbmSunlight(int fps) {
+ putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps));
+ }
+
+ void setRefreshRateInHbmHdr(int fps) {
+ putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
+ }
+
void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
String thresholds = toPropertyValue(brightnessThresholds);
@@ -1641,11 +1972,14 @@
}
}
- private void setBrightness(int brightness) {
- Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS,
- brightness);
- mInjector.notifyBrightnessChanged();
- waitForIdleSync();
+ private void setBrightness(int brightness, int adjustedBrightness, DisplayListener listener) {
+ float floatBri = BrightnessSynchronizer.brightnessIntToFloat(brightness);
+ float floatAdjBri = BrightnessSynchronizer.brightnessIntToFloat(adjustedBrightness);
+
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
}
private void setPeakRefreshRate(float fps) {
@@ -1709,27 +2043,6 @@
}
@Override
- public void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- if (mBrightnessObserver != null) {
- throw new IllegalStateException("Tried to register a second brightness observer");
- }
- mBrightnessObserver = observer;
- }
-
- @Override
- public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- mBrightnessObserver = null;
- }
-
- void notifyBrightnessChanged() {
- if (mBrightnessObserver != null) {
- mBrightnessObserver.dispatchChange(false /*selfChange*/, DISPLAY_BRIGHTNESS_URI);
- }
- }
-
- @Override
public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
mPeakRefreshRateObserver = observer;
@@ -1748,6 +2061,11 @@
return false;
}
+ @Override
+ public IThermalService getThermalService() {
+ return null;
+ }
+
void notifyPeakRefreshRateChanged() {
if (mPeakRefreshRateObserver != null) {
mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index cc3591c8..aca8632 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -20,6 +20,8 @@
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.spy;
@@ -124,6 +126,7 @@
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
DEFAULT_MAX, null, () -> {}, mContextSpy);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+ assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@Test
@@ -135,6 +138,7 @@
hbmc.setAutoBrightnessEnabled(true);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+ assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 70641c2..b811e28 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -129,6 +129,7 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
+import android.net.wifi.WifiInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.INetworkManagementService;
@@ -226,13 +227,13 @@
private static final long TEST_START = 1194220800000L;
private static final String TEST_IFACE = "test0";
- private static final String TEST_SSID = "AndroidAP";
+ private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey";
private static final String TEST_IMSI = "310210";
private static final int TEST_SUB_ID = 42;
private static final Network TEST_NETWORK = mock(Network.class, CALLS_REAL_METHODS);
- private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID);
+ private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_WIFI_NETWORK_KEY);
private static NetworkTemplate sTemplateCarrierMetered =
buildTemplateCarrierMetered(TEST_IMSI);
@@ -1986,9 +1987,8 @@
assertEquals("Unexpected template match rule in network policies",
NetworkTemplate.MATCH_CARRIER,
actualPolicy.template.getMatchRule());
- assertEquals("Unexpected subscriberId match rule in network policies",
- NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT,
- actualPolicy.template.getSubscriberIdMatchRule());
+ assertTrue("Unexpected subscriberIds size in network policies",
+ actualPolicy.template.getSubscriberIds().size() > 0);
assertEquals("Unexpected template meteredness in network policies",
METERED_YES, actualPolicy.template.getMeteredness());
}
@@ -2003,9 +2003,8 @@
assertEquals("Unexpected template match rule in network policies",
NetworkTemplate.MATCH_WIFI,
actualPolicy.template.getMatchRule());
- assertEquals("Unexpected subscriberId match rule in network policies",
- NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_ALL,
- actualPolicy.template.getSubscriberIdMatchRule());
+ assertEquals("Unexpected subscriberIds size in network policies",
+ actualPolicy.template.getSubscriberIds().size(), 0);
assertEquals("Unexpected template meteredness in network policies",
METERED_NO, actualPolicy.template.getMeteredness());
}
@@ -2098,10 +2097,13 @@
}
private static NetworkStateSnapshot buildWifi() {
+ WifiInfo mockWifiInfo = mock(WifiInfo.class);
+ when(mockWifiInfo.makeCopy(anyLong())).thenReturn(mockWifiInfo);
+ when(mockWifiInfo.getCurrentNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
final LinkProperties prop = new LinkProperties();
prop.setInterfaceName(TEST_IFACE);
final NetworkCapabilities networkCapabilities = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_WIFI).setSsid(TEST_SSID).build();
+ .addTransportType(TRANSPORT_WIFI).setTransportInfo(mockWifiInfo).build();
return new NetworkStateSnapshot(TEST_NETWORK, networkCapabilities, prop,
null /*subscriberId*/, TYPE_WIFI);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
index acd3fca..3261dfa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
@@ -125,7 +125,7 @@
public void checkDeviceIdentifierAccess_hasPrivilegedPermission_returnsGranted() {
// Apps with the READ_PRIVILEGED_PHONE_STATE permission should have access to device
// identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
APP_PID, APP_UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -140,7 +140,7 @@
public void checkDeviceIdentifierAccess_hasAppOp_returnsGranted() {
// Apps that have been granted the READ_DEVICE_IDENTIFIERS appop should have access to
// device identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS),
eq(APP_UID), eq(mPackageName), any(), any())).thenReturn(
AppOpsManager.MODE_ALLOWED);
@@ -156,7 +156,7 @@
public void checkDeviceIdentifierAccess_hasDpmAccess_returnsGranted() {
// Apps that pass a DevicePolicyManager device / profile owner check should have access to
// device identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mDevicePolicyManager.hasDeviceIdentifierAccess(mPackageName, APP_PID,
APP_UID)).thenReturn(true);
@@ -236,7 +236,7 @@
// both the permission and the appop must be granted. If the permission is granted but the
// appop is not then AppOpsManager#MODE_IGNORED should be returned to indicate that this
// should be a silent failure.
- setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
setPackageTargetSdk(Build.VERSION_CODES.Q);
grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null);
@@ -256,7 +256,7 @@
// Apps targeting R+ with just the READ_PHONE_STATE permission granted should not have
// access to the phone number; PERMISSION_DENIED should be returned both with and without
// the appop granted since this check should be skipped for target SDK R+.
- setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null);
int resultWithoutAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess(
@@ -319,12 +319,79 @@
assertEquals(PackageManager.PERMISSION_GRANTED, resultWithAppop);
}
+ @Test
+ public void checkPhoneNumberAccess_providedUidDoesNotMatchPackageUid_throwsException()
+ throws Exception {
+ // An app can directly interact with one of the services that accepts a package name and
+ // returns a protected resource via a direct binder transact. This app could then provide
+ // the name of another app that targets pre-R, then determine if the app is installed based
+ // on whether the service throws an exception or not. While the app can provide the package
+ // name of another app, it cannot specify the package uid which is passed to the
+ // LegacyPermissionManager using Binder#getCallingUid. Ultimately this uid should then be
+ // compared against the actual uid of the package to ensure information about packages
+ // installed on the device is not leaked.
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID + 1);
+
+ assertThrows(SecurityException.class,
+ () -> mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName,
+ CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID));
+ }
+
+ @Test
+ public void checkPhoneNumberAccess_nullPackageNameSystemUid_returnsGranted() throws Exception {
+ // The platform can pass a null package name when checking if the platform itself has
+ // access to the device phone number(s) / identifier(s). This test ensures if a null package
+ // is provided, then the package uid check is skipped and the test is based on whether the
+ // the provided uid / pid has been granted the privileged permission.
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, -1);
+ when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ SYSTEM_PID, SYSTEM_UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(null,
+ CHECK_PHONE_NUMBER_MESSAGE, null, SYSTEM_PID, SYSTEM_UID);
+
+ assertEquals(PackageManager.PERMISSION_GRANTED, result);
+ }
+
+ @Test
+ public void checkPhoneNumberAccess_systemUidMismatchPackageUid_returnsGranted()
+ throws Exception {
+ // When the platform is checking device phone number / identifier access checks for other
+ // components on the platform, a uid less than the first application UID is provided; this
+ // test verifies the package uid check is skipped and access is still granted with the
+ // privileged permission.
+ int telephonyUid = SYSTEM_UID + 1;
+ int telephonyPid = SYSTEM_PID + 1;
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, -1);
+ when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ telephonyPid, telephonyUid)).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName,
+ CHECK_PHONE_NUMBER_MESSAGE, null, telephonyPid, telephonyUid);
+
+ assertEquals(PackageManager.PERMISSION_GRANTED, result);
+ }
+
/**
* Configures device identifier access tests to fail; tests verifying access should individually
* set an access check to succeed to verify access when that condition is met.
*/
private void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid) {
- setupAccessTest(callingPid, callingUid);
+ setupCheckDeviceIdentifierAccessTest(callingPid, callingUid, callingUid);
+ }
+
+ /**
+ * Configures device identifier access tests to fail; tests verifying access should individually
+ * set an access check to succeed to verify access when that condition is met.
+ *
+ * <p>To prevent leaking package information, access checks for package UIDs >= {@link
+ * android.os.Process#FIRST_APPLICATION_UID} must ensure the provided uid matches the uid of
+ * the package being checked; to ensure this check is successful, this method accepts the
+ * {@code packageUid} to be used for the package being checked.
+ */
+ public void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid,
+ int packageUid) {
+ setupAccessTest(callingPid, callingUid, packageUid);
when(mDevicePolicyManager.hasDeviceIdentifierAccess(anyString(), anyInt(),
anyInt())).thenReturn(false);
@@ -333,11 +400,26 @@
}
/**
- * Configures phone number access tests to fail; tests verifying access should individually set
- * an access check to succeed to verify access when that condition is met.
+ * Configures phone number access tests to fail; tests verifying access should individually
+ * set an access check to succeed to verify access when that condition is set.
+ *
*/
private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid) throws Exception {
- setupAccessTest(callingPid, callingUid);
+ setupCheckPhoneNumberAccessTest(callingPid, callingUid, callingUid);
+ }
+
+ /**
+ * Configures phone number access tests to fail; tests verifying access should individually set
+ * an access check to succeed to verify access when that condition is met.
+ *
+ * <p>To prevent leaking package information, access checks for package UIDs >= {@link
+ * android.os.Process#FIRST_APPLICATION_UID} must ensure the provided uid matches the uid of
+ * the package being checked; to ensure this check is successful, this method accepts the
+ * {@code packageUid} to be used for the package being checked.
+ */
+ private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid, int packageUid)
+ throws Exception {
+ setupAccessTest(callingPid, callingUid, packageUid);
setPackageTargetSdk(Build.VERSION_CODES.R);
}
@@ -345,9 +427,10 @@
* Configures the common mocks for any access tests using the provided {@code callingPid}
* and {@code callingUid}.
*/
- private void setupAccessTest(int callingPid, int callingUid) {
+ private void setupAccessTest(int callingPid, int callingUid, int packageUid) {
when(mInjector.getCallingPid()).thenReturn(callingPid);
when(mInjector.getCallingUid()).thenReturn(callingUid);
+ when(mInjector.getPackageUidForUser(anyString(), anyInt())).thenReturn(packageUid);
when(mInjector.checkPermission(anyString(), anyInt(), anyInt())).thenReturn(
PackageManager.PERMISSION_DENIED);
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
index 5012ca9..6e3f754 100644
--- a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
@@ -210,7 +210,7 @@
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return mNotifierMock;
}
@@ -298,6 +298,7 @@
BatteryStats.SERVICE_NAME)),
mInjector.createSuspendBlocker(mService, "testBlocker"),
null,
+ null,
null);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 5eabc1b..3d64d4b 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -215,7 +215,7 @@
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return mNotifierMock;
}
diff --git a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
index 11554c7..6334e9d 100644
--- a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
@@ -82,6 +82,8 @@
MockitoAnnotations.initMocks(this);
when(mUwbInjector.getVendorService()).thenReturn(mVendorService);
when(mUwbInjector.checkUwbRangingPermissionForDataDelivery(any(), any())).thenReturn(true);
+ when(mUwbInjector.isPersistedUwbStateEnabled()).thenReturn(true);
+ when(mUwbInjector.isAirplaneModeOn()).thenReturn(false);
when(mVendorService.asBinder()).thenReturn(mVendorServiceBinder);
mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 71c05b5..ea46eab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -32,6 +32,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
@@ -72,6 +73,7 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -1182,6 +1184,7 @@
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
mService.buzzBeepBlinkLocked(r);
+ verifyDelayedVibrate(mService.getVibratorHelper().createFallbackVibration(false));
// quiet update should stop making noise
mService.buzzBeepBlinkLocked(s);
@@ -1564,6 +1567,32 @@
}
@Test
+ public void testRingtoneInsistentBeep_canUpdate() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ mService.addNotification(ringtoneNotification);
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+ verifyDelayedVibrateLooped();
+ Mockito.reset(mVibrator);
+ Mockito.reset(mRingtonePlayer);
+
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+
+ // beep wasn't reset
+ verifyNeverBeep();
+ verifyNeverVibrate();
+ verify(mRingtonePlayer, never()).stopAsync();
+ verify(mVibrator, never()).cancel();
+ }
+
+ @Test
public void testCannotInterruptRingtoneInsistentBuzz() {
NotificationChannel ringtoneChannel =
new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index f9663f2..987236c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -65,6 +65,7 @@
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -1320,16 +1321,15 @@
APPROVAL_BY_COMPONENT);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- service.registerSystemService(cn, 0);
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onNullBinding(cn);
- return true;
- });
+ ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+ when(context.bindServiceAsUser(any(), captor.capture(), anyInt(), any()))
+ .thenAnswer(invocation -> {
+ captor.getValue().onNullBinding(cn);
+ return true;
+ });
service.registerSystemService(cn, 0);
- assertFalse(service.isBound(cn, 0));
+ verify(context).unbindService(captor.getValue());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f57c416..1ae219d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -516,7 +516,7 @@
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
- mWorkerHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
+ mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
@@ -2703,6 +2703,42 @@
}
@Test
+ public void testCrossUserSnooze() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 10);
+ mService.addNotification(r);
+ NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, 0);
+ mService.addNotification(r2);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+
+ verify(mWorkerHandler, never()).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ }
+
+ @Test
+ public void testSameUserSnooze() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 10);
+ mService.addNotification(r);
+ NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, 0);
+ mService.addNotification(r2);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(r2.getKey(), 1000, null, mListener);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ }
+
+ @Test
public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() throws Exception {
final NotificationRecord notification = generateNotificationRecord(
mTestNotificationChannel, 1, null, true);
@@ -3975,6 +4011,80 @@
}
@Test
+ public void testApplyAdjustmentsLogged() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r1 = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r1);
+ final NotificationRecord r2 = generateNotificationRecord(
+ mTestNotificationChannel, 2, null, true);
+ r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r2);
+
+ // Third notification that's NOT adjusted, just to make sure that doesn't get spuriously
+ // logged.
+ final NotificationRecord r3 = generateNotificationRecord(
+ mTestNotificationChannel, 3, null, true);
+ r3.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r3);
+
+ List<Adjustment> adjustments = new ArrayList<>();
+
+ // Test an adjustment that's associated with a ranking change and one that's not
+ Bundle signals1 = new Bundle();
+ signals1.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_HIGH);
+ Adjustment adjustment1 = new Adjustment(
+ r1.getSbn().getPackageName(), r1.getKey(), signals1, "",
+ r1.getUser().getIdentifier());
+ adjustments.add(adjustment1);
+
+ // This one wouldn't trigger a ranking change, but should still trigger a log.
+ Bundle signals2 = new Bundle();
+ signals2.putFloat(Adjustment.KEY_RANKING_SCORE, -0.5f);
+ Adjustment adjustment2 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals2, "",
+ r2.getUser().getIdentifier());
+ adjustments.add(adjustment2);
+
+ mBinderService.applyAdjustmentsFromAssistant(null, adjustments);
+ verify(mRankingHandler, times(1)).requestSort();
+
+ // Actually apply the adjustments & recalculate importance when run
+ doAnswer(invocationOnMock -> {
+ ((NotificationRecord) invocationOnMock.getArguments()[0])
+ .applyAdjustments();
+ ((NotificationRecord) invocationOnMock.getArguments()[0])
+ .calculateImportance();
+ return null;
+ }).when(mRankingHelper).extractSignals(any(NotificationRecord.class));
+
+ // Now make sure that when the sort happens, we actually log the changes.
+ mService.handleRankingSort();
+
+ // Even though the ranking score change is not meant to trigger a ranking update,
+ // during this process the package visibility & canShowBadge values are changing
+ // in all notifications, so all 3 seem to trigger a ranking change. Here we check instead
+ // that scheduleSendRankingUpdate is sent and that the relevant fields have been changed
+ // accordingly to confirm the adjustments happened to the 2 relevant notifications.
+ verify(handler, times(3)).scheduleSendRankingUpdate();
+ assertEquals(IMPORTANCE_HIGH, r1.getImportance());
+ assertTrue(r2.rankingScoreMatches(-0.5f));
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ mNotificationRecordLogger.event(0));
+ assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ mNotificationRecordLogger.event(1));
+ assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
+ assertEquals(2, mNotificationRecordLogger.get(1).getInstanceId());
+ }
+
+ @Test
public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addEnqueuedNotification(r);
@@ -4793,6 +4903,52 @@
}
@Test
+ public void testSetNotificationsShownFromListener_protectsCrossUserInformation()
+ throws RemoteException {
+ Notification.Builder nb = new Notification.Builder(
+ mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag" + System.currentTimeMillis(), UserHandle.PER_USER_RANGE, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE),
+ null, 0);
+ final NotificationRecord r =
+ new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ // no security exception!
+ mBinderService.setNotificationsShownFromListener(null, new String[] {r.getKey()});
+
+ verify(mAppUsageStats, never()).reportInterruptiveNotification(
+ anyString(), anyString(), anyInt());
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_protectsCrossUserInformation()
+ throws RemoteException {
+ Notification.Builder nb = new Notification.Builder(
+ mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag" + System.currentTimeMillis(), UserHandle.PER_USER_RANGE, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE),
+ null, 0);
+ final NotificationRecord r =
+ new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ // no security exception!
+ mBinderService.cancelNotificationsFromListener(null, new String[] {r.getKey()});
+
+ waitForIdle();
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+ @Test
public void testMaybeRecordInterruptionLocked_doesNotRecordTwice()
throws RemoteException {
final NotificationRecord r = generateNotificationRecord(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index 64fd19e..8a11798 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -45,6 +45,15 @@
groupInstanceId = groupId;
}
+ CallRecord(NotificationRecord r, int position, int buzzBeepBlink, InstanceId groupId) {
+ super(r, null);
+ this.position = position;
+ this.buzzBeepBlink = buzzBeepBlink;
+ wasLogged = true;
+ event = NotificationReportedEvent.NOTIFICATION_ADJUSTED;
+ groupInstanceId = groupId;
+ }
+
CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) {
super(r, null);
wasLogged = true;
@@ -75,6 +84,12 @@
}
@Override
+ public void logNotificationAdjusted(NotificationRecord r, int position, int buzzBeepBlink,
+ InstanceId groupId) {
+ mCalls.add(new CallRecord(r, position, buzzBeepBlink, groupId));
+ }
+
+ @Override
public void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
mCalls.add(new CallRecord(r, event));
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index bf0ed71..66d1577 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -91,6 +91,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.service.notification.ConversationChannelWrapper;
@@ -376,27 +377,19 @@
when(mPm.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
}
- private static NotificationChannel createNotificationChannel(String id, String name,
- int importance) {
- NotificationChannel channel = new NotificationChannel(id, name, importance);
- channel.setSound(SOUND_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT);
- return channel;
- }
-
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
// Setup package notifications.
String package0 = "test.package.user0";
int uid0 = 1001;
setUpPackageWithUid(package0, uid0);
- NotificationChannel channel0 = createNotificationChannel("id0", "name0", IMPORTANCE_HIGH);
+ NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
String package10 = "test.package.user10";
int uid10 = 1001001;
setUpPackageWithUid(package10, uid10);
- NotificationChannel channel10 = createNotificationChannel("id10", "name10",
- IMPORTANCE_HIGH);
+ NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));
ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10);
@@ -421,7 +414,7 @@
String package0 = "test.package.user0";
int uid0 = 1001;
setUpPackageWithUid(package0, uid0);
- NotificationChannel channel0 = createNotificationChannel("id0", "name0", IMPORTANCE_HIGH);
+ NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0);
@@ -514,8 +507,9 @@
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 = createNotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setDescription("descriptions for all");
channel2.setSound(SOUND_URI, mAudioAttributes);
channel2.enableLights(true);
@@ -524,7 +518,7 @@
channel2.enableVibration(false);
channel2.setGroup(ncg.getId());
channel2.setLightColor(Color.BLUE);
- NotificationChannel channel3 = createNotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
+ NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
channel3.enableVibration(true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -631,8 +625,7 @@
}
@Test
- public void testRestoreXml_withNonExistentCanonicalizedSoundUri_ignoreChannel()
- throws Exception {
+ public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
Thread.sleep(3000);
doReturn(null)
.when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
@@ -650,7 +643,7 @@
NotificationChannel actualChannel = mHelper.getNotificationChannel(
PKG_N_MR1, UID_N_MR1, channel.getId(), false);
- assertNull(actualChannel);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
}
@@ -659,8 +652,7 @@
* handle its restore properly.
*/
@Test
- public void testRestoreXml_withUncanonicalizedNonLocalSoundUri_ignoreChannel()
- throws Exception {
+ public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
// Not a local uncanonicalized uri, simulating that it fails to exist locally
doReturn(null)
.when(mTestIContentProvider).canonicalize(any(), eq(SOUND_URI));
@@ -679,7 +671,7 @@
backupWithUncanonicalizedSoundUri.getBytes(), true, UserHandle.USER_SYSTEM);
NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, id, false);
- assertNull(actualChannel);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
}
@Test
@@ -703,11 +695,11 @@
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
- createNotificationChannel("id2", "name2", IMPORTANCE_HIGH);
+ new NotificationChannel("id2", "name2", IMPORTANCE_HIGH);
NotificationChannel channel3 =
- createNotificationChannel("id3", "name3", IMPORTANCE_LOW);
+ new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
channel3.setGroup(ncg.getId());
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -3062,7 +3054,7 @@
@Test
public void testChannelXml_backupDefaultApp() throws Exception {
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
mHelper.createNotificationChannel(PKG_O, UID_O, channel1, true, false);
@@ -3343,7 +3335,7 @@
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
- PKG_P, UID_P, createNotificationChannel("id", "id", 2), true, false);
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
assertTrue(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id"));
assertFalse(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id"));
}
@@ -3354,7 +3346,7 @@
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
- PKG_P, UID_P, createNotificationChannel("id", "id", 2), true, false);
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
mHelper.deleteNotificationChannel(PKG_P, UID_P, "id");
NotificationChannel nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
assertTrue(DateUtils.isToday(nc1.getDeletedTimeMs()));
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index 222c692..6b36fe8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -69,7 +69,7 @@
@Before
public void setUp() {
- mDetector = new SingleKeyGestureDetector(mContext);
+ mDetector = new SingleKeyGestureDetector();
initSingleKeyGestureRules();
mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50;
mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50;
@@ -78,7 +78,7 @@
}
private void initSingleKeyGestureRules() {
- mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER,
+ mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(mContext, KEYCODE_POWER,
KEY_LONGPRESS | KEY_VERYLONGPRESS) {
@Override
int getMaxMultiPressCount() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 0d177c1..19f9b75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -40,8 +41,10 @@
import android.app.WaitResult;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.ConditionVariable;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
@@ -187,6 +190,24 @@
verify(taskChangeNotifier, never()).notifyActivityDismissingDockedRootTask();
}
+ /** Ensures that the calling package name passed to client complies with package visibility. */
+ @Test
+ public void testFilteredReferred() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setLaunchedFromPackage("other.package").setCreateTask(true).build();
+ assertNotNull(activity.launchedFromPackage);
+ try {
+ mSupervisor.realStartActivityLocked(activity, activity.app, false /* andResume */,
+ false /* checkConfig */);
+ } catch (RemoteException ignored) {
+ }
+ verify(activity).getFilteredReferrer(eq(activity.launchedFromPackage));
+
+ activity.deliverNewIntentLocked(ActivityBuilder.DEFAULT_FAKE_UID,
+ new Intent(), null /* intentGrants */, "other.package2");
+ verify(activity).getFilteredReferrer(eq("other.package2"));
+ }
+
/**
* Ensures that notify focus task changes.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 12fc2f4..51e289f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1565,6 +1565,12 @@
mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
assertTrue(displayRotation.updateRotationUnchecked(false));
+ // Rotation can be updated if the policy is not ok to animate (e.g. going to sleep).
+ mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
+ displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
+ ((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false;
+ assertTrue(displayRotation.updateRotationUnchecked(false));
+
// Rotation can be updated if the recents animation is animating but it is not on top, e.g.
// switching activities in different orientations by quickstep gesture.
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
@@ -2166,6 +2172,21 @@
}
@Test
+ public void testKeyguardGoingAwayWhileAodShown() {
+ mDisplayContent.getDisplayPolicy().setAwake(true);
+
+ final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
+ final ActivityRecord activity = appWin.mActivityRecord;
+
+ mAtm.mKeyguardController.setKeyguardShown(true /* keyguardShowing */,
+ true /* aodShowing */);
+ assertFalse(activity.isVisibleRequested());
+
+ mAtm.mKeyguardController.keyguardGoingAway(0 /* flags */);
+ assertTrue(activity.isVisibleRequested());
+ }
+
+ @Test
public void testRemoveRootTaskInWindowingModes() {
removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes(
WINDOWING_MODE_FULLSCREEN));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index f2418c6..a8ede13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -82,7 +82,7 @@
mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken,
dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
null /* options */);
- return true;
+ return dc.getImeContainer().getConfiguration();
}).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG),
anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index c4cccf0..1924760 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -104,7 +104,8 @@
mPolicy.addNonHighRefreshRatePackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertEquals(LOW_REFRESH_RATE,
+ mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.removeNonHighRefreshRatePackage("com.android.test");
@@ -165,7 +166,8 @@
assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
assertEquals(HI_REFRESH_RATE,
mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertEquals(LOW_REFRESH_RATE,
+ mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -180,7 +182,8 @@
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
assertEquals(HI_REFRESH_RATE,
mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertEquals(LOW_REFRESH_RATE,
+ mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -257,7 +260,8 @@
mPolicy.addNonHighRefreshRatePackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertEquals(LOW_REFRESH_RATE,
+ mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d6a8401..ab496cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -797,6 +797,9 @@
public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
}
@Override
+ public void onImeDrawnOnTask(int taskId) throws RemoteException {
+ }
+ @Override
public void onAppSplashScreenViewRemoved(int taskId) {
}
};
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5880899..611b3f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -801,6 +801,7 @@
private int mConfigChanges;
private int mLaunchedFromPid;
private int mLaunchedFromUid;
+ private String mLaunchedFromPackage;
private WindowProcessController mWpc;
private Bundle mIntentExtras;
private boolean mOnTop = false;
@@ -911,6 +912,11 @@
return this;
}
+ ActivityBuilder setLaunchedFromPackage(String packageName) {
+ mLaunchedFromPackage = packageName;
+ return this;
+ }
+
ActivityBuilder setUseProcess(WindowProcessController wpc) {
mWpc = wpc;
return this;
@@ -1000,6 +1006,7 @@
final ActivityRecord activity = new ActivityRecord.Builder(mService)
.setLaunchedFromPid(mLaunchedFromPid)
.setLaunchedFromUid(mLaunchedFromUid)
+ .setLaunchedFromPackage(mLaunchedFromPackage)
.setIntent(intent)
.setActivityInfo(aInfo)
.setActivityOptions(options)
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1579aaf..8cb0909 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1195,7 +1195,8 @@
} else if (mCurrentFunctions == UsbManager.FUNCTION_MIDI) {
titleRes = com.android.internal.R.string.usb_midi_notification_title;
id = SystemMessage.NOTE_USB_MIDI;
- } else if (mCurrentFunctions == UsbManager.FUNCTION_RNDIS) {
+ } else if ((mCurrentFunctions == UsbManager.FUNCTION_RNDIS)
+ || (mCurrentFunctions == UsbManager.FUNCTION_NCM)) {
titleRes = com.android.internal.R.string.usb_tether_notification_title;
id = SystemMessage.NOTE_USB_TETHER;
} else if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) {
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 7b6ccd3..0c65cc4 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -45,6 +45,7 @@
import android.service.usb.UsbUserPermissionsManagerProto;
import android.util.ArrayMap;
import android.util.AtomicFile;
+import android.util.EventLog;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.TypedXmlPullParser;
@@ -74,6 +75,8 @@
private static final String TAG = UsbUserPermissionManager.class.getSimpleName();
private static final boolean DEBUG = false;
+ private static final int SNET_EVENT_LOG_ID = 0x534e4554;
+
@GuardedBy("mLock")
/** Mapping of USB device name to list of UIDs with permissions for the device
* Each entry lasts until device is disconnected*/
@@ -689,8 +692,11 @@
try {
ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
if (aInfo.uid != uid) {
- throw new IllegalArgumentException("package " + packageName
+ Slog.w(TAG, "package " + packageName
+ " does not match caller's uid " + uid);
+ EventLog.writeEvent(SNET_EVENT_LOG_ID, "180104273", -1, "");
+ throw new IllegalArgumentException("package " + packageName
+ + " not found");
}
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("package " + packageName + " not found");
diff --git a/services/uwb/java/com/android/server/uwb/UwbInjector.java b/services/uwb/java/com/android/server/uwb/UwbInjector.java
index 64f1da1..a7a0500 100644
--- a/services/uwb/java/com/android/server/uwb/UwbInjector.java
+++ b/services/uwb/java/com/android/server/uwb/UwbInjector.java
@@ -21,10 +21,13 @@
import android.annotation.NonNull;
import android.content.AttributionSource;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.PermissionChecker;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.provider.Settings;
+import android.uwb.AdapterState;
import android.uwb.IUwbAdapter;
@@ -80,4 +83,23 @@
mContext, UWB_RANGING, -1, attributionSource, message);
return permissionCheckResult == PERMISSION_GRANTED;
}
+
+ /** Returns true if UWB state saved in Settings is enabled. */
+ public boolean isPersistedUwbStateEnabled() {
+ final ContentResolver cr = mContext.getContentResolver();
+ try {
+ return Settings.Global.getInt(cr, Settings.Global.UWB_ENABLED)
+ == AdapterState.STATE_ENABLED_ACTIVE;
+ } catch (Settings.SettingNotFoundException e) {
+ Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED,
+ AdapterState.STATE_ENABLED_ACTIVE);
+ return true;
+ }
+ }
+
+ /** Returns true if airplane mode is turned on. */
+ public boolean isAirplaneModeOn() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
}
diff --git a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
index 4dd26a6..70d6aab 100644
--- a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
+++ b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
@@ -18,13 +18,19 @@
import android.annotation.NonNull;
import android.content.AttributionSource;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Binder;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
+import android.uwb.AdapterState;
import android.uwb.IUwbAdapter;
import android.uwb.IUwbAdapterStateCallbacks;
import android.uwb.IUwbRangingCallbacks;
@@ -225,13 +231,20 @@
mVendorUwbAdapter = null;
}
- private synchronized IUwbAdapter getVendorUwbAdapter() throws IllegalStateException {
+ private synchronized IUwbAdapter getVendorUwbAdapter()
+ throws IllegalStateException, RemoteException {
if (mVendorUwbAdapter != null) return mVendorUwbAdapter;
mVendorUwbAdapter = mUwbInjector.getVendorService();
if (mVendorUwbAdapter == null) {
throw new IllegalStateException("No vendor service found!");
}
Log.i(TAG, "Retrieved vendor service");
+ long token = Binder.clearCallingIdentity();
+ try {
+ mVendorUwbAdapter.setEnabled(isEnabled());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
linkToVendorServiceDeath();
return mVendorUwbAdapter;
}
@@ -239,6 +252,7 @@
UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) {
mContext = context;
mUwbInjector = uwbInjector;
+ registerAirplaneModeReceiver();
}
private void enforceUwbPrivilegedPermission() {
@@ -320,6 +334,34 @@
@Override
public synchronized void setEnabled(boolean enabled) throws RemoteException {
- getVendorUwbAdapter().setEnabled(enabled);
+ persistUwbState(enabled);
+ getVendorUwbAdapter().setEnabled(isEnabled());
+ }
+
+ private void persistUwbState(boolean enabled) {
+ final ContentResolver cr = mContext.getContentResolver();
+ int state = enabled ? AdapterState.STATE_ENABLED_ACTIVE : AdapterState.STATE_DISABLED;
+ Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED, state);
+ }
+
+ private void registerAirplaneModeReceiver() {
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleAirplaneModeEvent();
+ }
+ }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ }
+
+ private void handleAirplaneModeEvent() {
+ try {
+ getVendorUwbAdapter().setEnabled(isEnabled());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to set UWB Adapter state.", e);
+ }
+ }
+
+ private boolean isEnabled() {
+ return mUwbInjector.isPersistedUwbStateEnabled() && !mUwbInjector.isAirplaneModeOn();
}
}
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 702ddb1..af0eca9 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -18,5 +18,9 @@
name: "services.voiceinteraction",
defaults: ["platform_service_defaults"],
srcs: [":services.voiceinteraction-sources"],
- libs: ["services.core"],
+ libs: [
+ "services.core",
+ "android.hardware.power-V1-java",
+ "android.hardware.power-V1.0-java",
+ ],
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index a9aeb98..4dc83ae 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -269,13 +269,11 @@
Slog.v(TAG, "cancelLocked");
clearDebugHotwordLoggingTimeoutLocked();
mDebugHotwordLogging = false;
- if (mRemoteHotwordDetectionService.isBound()) {
- mRemoteHotwordDetectionService.unbind();
- LocalServices.getService(PermissionManagerServiceInternal.class)
- .setHotwordDetectionServiceProvider(null);
- mIdentity = null;
- updateServiceUidForAudioPolicy(Process.INVALID_UID);
- }
+ mRemoteHotwordDetectionService.unbind();
+ LocalServices.getService(PermissionManagerServiceInternal.class)
+ .setHotwordDetectionServiceProvider(null);
+ mIdentity = null;
+ updateServiceUidForAudioPolicy(Process.INVALID_UID);
mCancellationTaskFuture.cancel(/* may interrupt */ true);
if (mAudioFlinger != null) {
mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index cc021a9..08e9703 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -43,11 +43,13 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
+import android.hardware.power.Boost;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -62,6 +64,7 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.am.AssistDataRequester;
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
@@ -70,13 +73,20 @@
import com.android.server.wm.ActivityAssistInfo;
import java.io.PrintWriter;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
final class VoiceInteractionSessionConnection implements ServiceConnection,
AssistDataRequesterCallbacks {
- final static String TAG = "VoiceInteractionServiceManager";
+ static final String TAG = "VoiceInteractionServiceManager";
+ static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt(
+ System.getProperty("vendor.powerhal.interaction.max", "200"));
+ static final int BOOST_TIMEOUT_MS = 300;
+ // TODO: To avoid ap doesn't call hide, only 10 secs for now, need a better way to manage it
+ // in the future.
+ static final int MAX_POWER_BOOST_TIMEOUT = 10_000;
final IBinder mToken = new Binder();
final Object mLock;
@@ -104,6 +114,45 @@
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>();
AssistDataRequester mAssistDataRequester;
+ private final PowerManagerInternal mPowerManagerInternal;
+ private PowerBoostSetter mSetPowerBoostRunnable;
+ private final Handler mFgHandler;
+
+ class PowerBoostSetter implements Runnable {
+
+ private boolean mCanceled;
+ private final Instant mExpiryTime;
+
+ PowerBoostSetter(Instant expiryTime) {
+ mExpiryTime = expiryTime;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return;
+ }
+ // To avoid voice interaction service does not call hide to cancel setting
+ // power boost. We will cancel set boost when reaching the max timeout.
+ if (Instant.now().isBefore(mExpiryTime)) {
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, BOOST_TIMEOUT_MS);
+ if (mSetPowerBoostRunnable != null) {
+ mFgHandler.postDelayed(mSetPowerBoostRunnable, POWER_BOOST_TIMEOUT_MS);
+ }
+ } else {
+ Slog.w(TAG, "Reset power boost INTERACTION because reaching max timeout.");
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
+ }
+ }
+ }
+
+ void cancel() {
+ synchronized (mLock) {
+ mCanceled = true;
+ }
+ }
+ }
IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -152,7 +201,9 @@
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOps = context.getSystemService(AppOpsManager.class);
+ mFgHandler = FgThread.getHandler();
mAssistDataRequester = new AssistDataRequester(mContext, mIWindowManager,
(AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
@@ -255,6 +306,13 @@
mPendingHandleAssistWithoutData = topActivities;
}
}
+ // remove if already existing one.
+ if (mSetPowerBoostRunnable != null) {
+ mSetPowerBoostRunnable.cancel();
+ }
+ mSetPowerBoostRunnable = new PowerBoostSetter(
+ Instant.now().plusMillis(MAX_POWER_BOOST_TIMEOUT));
+ mFgHandler.post(mSetPowerBoostRunnable);
mCallback.onSessionShown(this);
return true;
}
@@ -420,6 +478,12 @@
} catch (RemoteException e) {
}
}
+ if (mSetPowerBoostRunnable != null) {
+ mSetPowerBoostRunnable.cancel();
+ mSetPowerBoostRunnable = null;
+ }
+ // A negative value indicates canceling previous boost.
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
mCallback.onSessionHidden(this);
}
if (mFullyBound) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 2b855e9..467084a 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -781,6 +781,21 @@
"android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE";
/**
+ * The Telecom call ID of the conference an existing connection should be added to. This is
+ * required when {@link com.android.services.telephony.TelephonyConnectionService} adds a
+ * {@link Conference} to Telecom using the
+ * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection, Conference)}
+ * API. That API specifies a parent conference associated with the new existing connection
+ * being added, and there is no equivalent as part of the {@link RemoteConnectionService} API.
+ * This extra key is used to stack the ID of the conference to which the existing connection
+ * will be added so that Telecom can link it up correctly when the {@link RemoteConference}
+ * is added to Telecom by the connection manager.
+ * @hide
+ */
+ public static final String EXTRA_ADD_TO_CONFERENCE_ID =
+ "android.telecom.extra.ADD_TO_CONFERENCE_ID";
+
+ /**
* Extra key set from a {@link ConnectionService} when using the remote connection APIs
* (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle,
* ConnectionRequest, boolean)}) to create a remote connection. Provides the receiving
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 02bd001..bc0a146 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -23,6 +23,8 @@
import android.os.Bundle;
import android.util.ArrayMap;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -115,6 +117,7 @@
public static final int SDK_VERSION_R = 30;
// A Map allows us to track each Call by its Telecom-specified call ID
+ @GuardedBy("mLock")
private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
// A List allows us to keep the Calls in a stable iteration order so that casually developed
@@ -154,7 +157,7 @@
return;
}
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call == null) {
call = new Call(this, parcelableCall.getId(), mInCallAdapter,
parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
@@ -191,14 +194,14 @@
if (mTargetSdkVersion < SDK_VERSION_R
&& parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
Log.i(this, "removing audio processing call during update for sdk compatibility");
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call != null) {
internalRemoveCall(call);
}
return;
}
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call != null) {
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
@@ -215,8 +218,14 @@
}
}
+ Call getCallById(String callId) {
+ synchronized (mLock) {
+ return mCallByTelecomCallId.get(callId);
+ }
+ }
+
final void internalSetPostDialWait(String telecomId, String remaining) {
- Call call = mCallByTelecomCallId.get(telecomId);
+ Call call = getCallById(telecomId);
if (call != null) {
call.internalSetPostDialWait(remaining);
}
@@ -230,7 +239,7 @@
}
final Call internalGetCallByTelecomId(String telecomId) {
- return mCallByTelecomCallId.get(telecomId);
+ return getCallById(telecomId);
}
final void internalBringToForeground(boolean showDialpad) {
@@ -249,35 +258,35 @@
}
final void internalOnConnectionEvent(String telecomId, String event, Bundle extras) {
- Call call = mCallByTelecomCallId.get(telecomId);
+ Call call = getCallById(telecomId);
if (call != null) {
call.internalOnConnectionEvent(event, extras);
}
}
final void internalOnRttUpgradeRequest(String callId, int requestId) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnRttUpgradeRequest(requestId);
}
}
final void internalOnRttInitiationFailure(String callId, int reason) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnRttInitiationFailure(reason);
}
}
final void internalOnHandoverFailed(String callId, int error) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnHandoverFailed(error);
}
}
final void internalOnHandoverComplete(String callId) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnHandoverComplete();
}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index bf6a6ef7..efe35d2 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -239,13 +239,9 @@
conference.addConnection(c);
}
}
- if (conference.getConnections().size() == 0) {
- // A conference was created, but none of its connections are ones that have been
- // created by, and therefore being tracked by, this remote connection service. It
- // is of no interest to us.
- Log.d(this, "addConferenceCall - skipping");
- return;
- }
+ // We used to skip adding empty conferences; however in the world of IMS conference
+ // calls we need to add them to the remote connection service because they will always
+ // start with no participants.
conference.setState(parcel.getState());
conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
@@ -379,6 +375,8 @@
@Override
public void addExistingConnection(String callId, ParcelableConnection connection,
Session.Info sessionInfo) {
+ Log.i(RemoteConnectionService.this, "addExistingConnection: callId=%s, conn=%s", callId,
+ connection);
String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
getOpPackageName();
int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo()
@@ -390,6 +388,20 @@
Bundle newExtras = new Bundle();
newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
connection.getPhoneAccount());
+ if (connection.getParentCallId() != null) {
+ RemoteConference parentConf = mConferenceById.get(connection.getParentCallId());
+ // If there is a parent being set, we need to stash the conference ID here.
+ // Telephony can add an existing connection while specifying a parent conference.
+ // There is no equivalent version of that operation as part of the remote connection
+ // API, so we will stash the pre-defined parent's ID in the extras. When the
+ // connectionmanager copies over the extras from the remote connection to the
+ // actual one, it'll get passed to Telecom so that it can make the association.
+ if (parentConf != null) {
+ newExtras.putString(Connection.EXTRA_ADD_TO_CONFERENCE_ID, parentConf.getId());
+ Log.i(this, "addExistingConnection: stash parent of %s as %s",
+ connection.getParentCallId(), parentConf.getId());
+ }
+ }
remoteConnection.putExtras(newExtras);
mConnectionById.put(callId, remoteConnection);
remoteConnection.registerCallback(new RemoteConnection.Callback() {
diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
index e2d62f8..6d9b3210 100644
--- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java
+++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
@@ -17,7 +17,6 @@
package com.google.android.mms.util;
-import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -25,49 +24,15 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
-import android.os.Build;
import android.util.Log;
-import android.widget.Toast;
public final class SqliteWrapper {
private static final String TAG = "SqliteWrapper";
- private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
- = "unable to open database file";
private SqliteWrapper() {
// Forbidden being instantiated.
}
- // FIXME: It looks like outInfo.lowMemory does not work well as we expected.
- // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false.
- private static boolean isLowMemory(Context context) {
- if (null == context) {
- return false;
- }
-
- ActivityManager am = (ActivityManager)
- context.getSystemService(Context.ACTIVITY_SERVICE);
- ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
- am.getMemoryInfo(outInfo);
-
- return outInfo.lowMemory;
- }
-
- // FIXME: need to optimize this method.
- private static boolean isLowMemory(SQLiteException e) {
- return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
- }
-
- @UnsupportedAppUsage
- public static void checkSQLiteException(Context context, SQLiteException e) {
- if (isLowMemory(e)) {
- Toast.makeText(context, com.android.internal.R.string.low_memory,
- Toast.LENGTH_SHORT).show();
- } else {
- throw e;
- }
- }
-
@UnsupportedAppUsage
public static Cursor query(Context context, ContentResolver resolver, Uri uri,
String[] projection, String selection, String[] selectionArgs, String sortOrder) {
@@ -75,21 +40,10 @@
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when query: ", e);
- checkSQLiteException(context, e);
return null;
}
}
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static boolean requery(Context context, Cursor cursor) {
- try {
- return cursor.requery();
- } catch (SQLiteException e) {
- Log.e(TAG, "Catch a SQLiteException when requery: ", e);
- checkSQLiteException(context, e);
- return false;
- }
- }
@UnsupportedAppUsage
public static int update(Context context, ContentResolver resolver, Uri uri,
ContentValues values, String where, String[] selectionArgs) {
@@ -97,7 +51,6 @@
return resolver.update(uri, values, where, selectionArgs);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when update: ", e);
- checkSQLiteException(context, e);
return -1;
}
}
@@ -109,7 +62,6 @@
return resolver.delete(uri, where, selectionArgs);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when delete: ", e);
- checkSQLiteException(context, e);
return -1;
}
}
@@ -121,7 +73,6 @@
return resolver.insert(uri, values);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when insert: ", e);
- checkSQLiteException(context, e);
return null;
}
}
diff --git a/telephony/java/android/service/carrier/CarrierService.java b/telephony/java/android/service/carrier/CarrierService.java
index d06ec11..ae91d4d 100644
--- a/telephony/java/android/service/carrier/CarrierService.java
+++ b/telephony/java/android/service/carrier/CarrierService.java
@@ -15,6 +15,8 @@
package android.service.carrier;
import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -22,9 +24,12 @@
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.ResultReceiver;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyRegistryManager;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -87,7 +92,54 @@
* PersistableBundle} may be overridden by the system's default configuration service.
* </p>
*
- * @param id contains details about the current carrier that can be used do decide what
+ * @param id contains details about the current carrier that can be used to decide what
+ * configuration values to return. Instead of using details like MCCMNC to decide
+ * current carrier, it also contains subscription carrier id
+ * {@link android.telephony.TelephonyManager#getSimCarrierId()}, a platform-wide
+ * unique identifier for each carrier, CarrierConfigService can directly use carrier
+ * id as the key to look up the carrier info.
+ * @return a {@link PersistableBundle} object containing the configuration or null if default
+ * values should be used.
+ * @deprecated use {@link #onLoadConfig(int, CarrierIdentifier)} instead.
+ */
+ @Deprecated
+ public abstract PersistableBundle onLoadConfig(CarrierIdentifier id);
+
+ /**
+ * Override this method to set carrier configuration on the given {@code subscriptionId}.
+ * <p>
+ * This method will be called by telephony services to get carrier-specific configuration
+ * values. The returned config will be saved by the system until,
+ * <ol>
+ * <li>The carrier app package is updated, or</li>
+ * <li>The carrier app requests a reload with
+ * {@link android.telephony.CarrierConfigManager#notifyConfigChangedForSubId
+ * notifyConfigChangedForSubId}.</li>
+ * </ol>
+ * This method can be called after a SIM card loads, which may be before or after boot.
+ * </p>
+ * <p>
+ * This method should not block for a long time. If expensive operations (e.g. network access)
+ * are required, this method can schedule the work and return null. Then, use
+ * {@link android.telephony.CarrierConfigManager#notifyConfigChangedForSubId
+ * notifyConfigChangedForSubId} to trigger a reload when the config is ready.
+ * </p>
+ * <p>
+ * Implementations should use the keys defined in {@link android.telephony.CarrierConfigManager
+ * CarrierConfigManager}. Any configuration values not set in the returned {@link
+ * PersistableBundle} may be overridden by the system's default configuration service.
+ * </p>
+ * <p>
+ * By default, this method just calls {@link #onLoadConfig(CarrierIdentifier)} with specified
+ * CarrierIdentifier {@code id}. Carrier app with target SDK
+ * {@link android.os.Build.VERSION_CODES#TIRAMISU} and above should override this method to
+ * load carrier configuration on the given {@code subscriptionId}.
+ * Note that {@link #onLoadConfig(CarrierIdentifier)} is still called prior to
+ * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ * </p>
+ *
+ * @param subscriptionId the subscription on which the carrier app should load configuration
+ * @param id contains details about the current carrier that can be used to decide what
* configuration values to return. Instead of using details like MCCMNC to decide
* current carrier, it also contains subscription carrier id
* {@link android.telephony.TelephonyManager#getSimCarrierId()}, a platform-wide
@@ -96,7 +148,11 @@
* @return a {@link PersistableBundle} object containing the configuration or null if default
* values should be used.
*/
- public abstract PersistableBundle onLoadConfig(CarrierIdentifier id);
+ @SuppressLint("NullableCollection")
+ @Nullable
+ public PersistableBundle onLoadConfig(int subscriptionId, @Nullable CarrierIdentifier id) {
+ return onLoadConfig(id);
+ }
/**
* Informs the system of an intentional upcoming carrier network change by
@@ -115,7 +171,12 @@
* active. Set this value to true to begin showing
* alternative UI and false to stop.
* @see android.telephony.TelephonyManager#hasCarrierPrivileges
+ * @deprecated use {@link #notifyCarrierNetworkChange(int, boolean)} instead.
+ * With no parameter to specify the subscription, this API will
+ * apply to all subscriptions that the carrier app has carrier
+ * privileges on.
*/
+ @Deprecated
public final void notifyCarrierNetworkChange(boolean active) {
TelephonyRegistryManager telephonyRegistryMgr =
(TelephonyRegistryManager) this.getSystemService(
@@ -126,6 +187,31 @@
}
/**
+ * Informs the system of an intentional upcoming carrier network change by a carrier app on the
+ * given {@code subscriptionId}. This call is optional and is only used to allow the system to
+ * provide alternative UI while telephony is performing an action that may result in
+ * intentional, temporary network lack of connectivity.
+ *
+ * <p>Based on the active parameter passed in, this method will either show or hide the
+ * alternative UI. There is no timeout associated with showing this UX, so a carrier app must
+ * be sure to call with active set to false sometime after calling with it set to true.
+ *
+ * <p>Requires Permission: calling app has carrier privileges.
+ *
+ * @param subscriptionId the subscription of the carrier network that trigger the change.
+ * @param active whether the carrier network change is or shortly will be active. Set this
+ * value to true to begin showing alternative UI and false to stop.
+ * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+ */
+ public final void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
+ TelephonyRegistryManager telephonyRegistryMgr = this.getSystemService(
+ TelephonyRegistryManager.class);
+ if (telephonyRegistryMgr != null) {
+ telephonyRegistryMgr.notifyCarrierNetworkChange(subscriptionId, active);
+ }
+ }
+
+ /**
* If overriding this method, call through to the super method for any unknown actions.
* {@inheritDoc}
*/
@@ -149,10 +235,16 @@
public static final String KEY_CONFIG_BUNDLE = "config_bundle";
@Override
- public void getCarrierConfig(CarrierIdentifier id, ResultReceiver result) {
+ public void getCarrierConfig(int phoneId, CarrierIdentifier id, ResultReceiver result) {
try {
+ int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ int[] subIds = SubscriptionManager.getSubId(phoneId);
+ if (!ArrayUtils.isEmpty(subIds)) {
+ // There should be at most one active subscription mapping to the phoneId.
+ subId = subIds[0];
+ }
Bundle data = new Bundle();
- data.putParcelable(KEY_CONFIG_BUNDLE, CarrierService.this.onLoadConfig(id));
+ data.putParcelable(KEY_CONFIG_BUNDLE, CarrierService.this.onLoadConfig(subId, id));
result.send(RESULT_OK, data);
} catch (Exception e) {
Log.e(LOG_TAG, "Error in onLoadConfig: " + e.getMessage(), e);
diff --git a/telephony/java/android/service/carrier/ICarrierService.aidl b/telephony/java/android/service/carrier/ICarrierService.aidl
index ac6f9614..054a280 100644
--- a/telephony/java/android/service/carrier/ICarrierService.aidl
+++ b/telephony/java/android/service/carrier/ICarrierService.aidl
@@ -29,5 +29,5 @@
interface ICarrierService {
/** @see android.service.carrier.CarrierService#onLoadConfig */
- oneway void getCarrierConfig(in CarrierIdentifier id, in ResultReceiver result);
+ oneway void getCarrierConfig(in int phoneId, in CarrierIdentifier id, in ResultReceiver result);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5ffe45f..5851ee1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -964,6 +964,21 @@
= "carrier_use_ims_first_for_emergency_bool";
/**
+ * When {@code true}, the determination of whether to place a call as an emergency call will be
+ * based on the known {@link android.telephony.emergency.EmergencyNumber}s for the SIM on which
+ * the call is being placed. In a dual SIM scenario, if Sim A has the emergency numbers
+ * 123, 456 and Sim B has the emergency numbers 789, and the user places a call on SIM A to 789,
+ * it will not be treated as an emergency call in this case.
+ * When {@code false}, the determination is based on the emergency numbers from all device SIMs,
+ * regardless of which SIM the call is being placed on. If Sim A has the emergency numbers
+ * 123, 456 and Sim B has the emergency numbers 789, and the user places a call on SIM A to 789,
+ * the call will be dialed as an emergency number, but with an unspecified routing.
+ * @hide
+ */
+ public static final String KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL =
+ "use_only_dialed_sim_ecc_list_bool";
+
+ /**
* When IMS instant lettering is available for a carrier (see
* {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines the list of characters
* which may not be contained in messages. Should be specified as a regular expression suitable
@@ -3718,6 +3733,17 @@
"esim_max_download_retry_attempts_int";
/**
+ * List of opportunistic carrier-ids associated with CBRS Primary SIM. When CBRS pSIM is
+ * inserted, opportunistic eSIM is download and this configuration is used for grouping pSIM
+ * and opportunistic eSIM. Also when a new CBRS pSIM is inserted, old opportunistic eSIMs are
+ * deleted using the carrier-ids in this configuration.
+ *
+ * @hide
+ */
+ public static final String KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY =
+ "opportunistic_carrier_ids_int_array";
+
+ /**
* Controls RSRP threshold at which OpportunisticNetworkService will decide whether
* the opportunistic network is good enough for internet data.
*/
@@ -5328,6 +5354,7 @@
sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true);
+ sDefaults.putBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false);
sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, "");
sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, "");
sDefaults.putString(KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING, "");
@@ -5774,6 +5801,7 @@
sDefaults.putString(KEY_SMDP_SERVER_ADDRESS_STRING, "");
sDefaults.putInt(KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT, 5);
sDefaults.putInt(KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT, 60);
+ sDefaults.putIntArray(KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY, new int[] {0});
/* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */
sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108);
/* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */
@@ -5821,6 +5849,7 @@
sDefaults.putInt(
KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG,
120000);
+ sDefaults.putAll(ImsServiceEntitlement.getDefaults());
sDefaults.putAll(Gps.getDefaults());
sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
new int[] {
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index e8633dd..947dc01 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -600,7 +600,7 @@
/** @hide */
public static int convertRssnrUnitFromTenDbToDB(int rssnr) {
- return rssnr / 10;
+ return (int) Math.floor((float) rssnr / 10);
}
/** @hide */
diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
index 9cb80f1..4884d54 100644
--- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
+++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
@@ -130,7 +131,10 @@
/**
* Set the builder object if require reporting on the system thresholds when device is idle.
*
- * <p>This can only used by the system caller. Requires permission
+ * <p>This is intended to be used by the system privileged caller only. When setting to
+ * {@code true}, signal strength update request through
+ * {@link TelephonyManager#setSignalStrengthUpdateRequest(SignalStrengthUpdateRequest)}
+ * will require permission
* {@link android.Manifest.permission#LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH}.
*
* @param isSystemThresholdReportingRequestedWhileIdle true if request reporting on the
@@ -138,6 +142,7 @@
* @return the builder to facilitate the chaining
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
public @NonNull Builder setSystemThresholdReportingRequestedWhileIdle(
boolean isSystemThresholdReportingRequestedWhileIdle) {
@@ -191,6 +196,7 @@
*
* @hide
*/
+ @SystemApi
public boolean isSystemThresholdReportingRequestedWhileIdle() {
return mIsSystemThresholdReportingRequestedWhileIdle;
}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 63a7acf..d6d6775 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -222,6 +222,11 @@
private boolean mAreUiccApplicationsEnabled = true;
/**
+ * The port index of the Uicc card.
+ */
+ private final int mPortIndex;
+
+ /**
* Public copy constructor.
* @hide
*/
@@ -274,6 +279,22 @@
int carrierId, int profileClass, int subType, @Nullable String groupOwner,
@Nullable UiccAccessRule[] carrierConfigAccessRules,
boolean areUiccApplicationsEnabled) {
+ this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
+ roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString,
+ cardId, isOpportunistic, groupUUID, isGroupDisabled, carrierId, profileClass,
+ subType, groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, 0);
+ }
+ /**
+ * @hide
+ */
+ public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
+ CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
+ Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
+ @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
+ boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
+ int carrierId, int profileClass, int subType, @Nullable String groupOwner,
+ @Nullable UiccAccessRule[] carrierConfigAccessRules,
+ boolean areUiccApplicationsEnabled, int portIndex) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -300,8 +321,8 @@
this.mGroupOwner = groupOwner;
this.mCarrierConfigAccessRules = carrierConfigAccessRules;
this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
+ this.mPortIndex = portIndex;
}
-
/**
* @return the subscription ID.
*/
@@ -737,6 +758,14 @@
public int getCardId() {
return this.mCardId;
}
+ /**
+ * Returns the port index of the SIM card which contains the subscription.
+ *
+ * @return the portIndex
+ */
+ public int getPortIndex() {
+ return this.mPortIndex;
+ }
/**
* Set whether the subscription's group is disabled.
@@ -783,6 +812,7 @@
UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR);
String cardString = source.readString();
int cardId = source.readInt();
+ int portId = source.readInt();
boolean isOpportunistic = source.readBoolean();
String groupUUID = source.readString();
boolean isGroupDisabled = source.readBoolean();
@@ -800,7 +830,7 @@
carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null,
mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId,
isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType,
- groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled);
+ groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, portId);
info.setAssociatedPlmns(ehplmns, hplmns);
return info;
}
@@ -830,6 +860,7 @@
dest.writeTypedArray(mNativeAccessRules, flags);
dest.writeString(mCardString);
dest.writeInt(mCardId);
+ dest.writeInt(mPortIndex);
dest.writeBoolean(mIsOpportunistic);
dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString());
dest.writeBoolean(mIsGroupDisabled);
@@ -876,6 +907,7 @@
+ " mnc=" + mMnc + " countryIso=" + mCountryIso + " isEmbedded=" + mIsEmbedded
+ " nativeAccessRules=" + Arrays.toString(mNativeAccessRules)
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
+ + " portIndex=" + mPortIndex
+ " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID
+ " isGroupDisabled=" + mIsGroupDisabled
+ " profileClass=" + mProfileClass
@@ -892,7 +924,7 @@
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
mCardId, mDisplayName, mCarrierName, mNativeAccessRules, mIsGroupDisabled,
- mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled);
+ mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex);
}
@Override
@@ -925,6 +957,7 @@
&& Objects.equals(mCountryIso, toCompare.mCountryIso)
&& Objects.equals(mCardString, toCompare.mCardString)
&& Objects.equals(mCardId, toCompare.mCardId)
+ && mPortIndex == toCompare.mPortIndex
&& Objects.equals(mGroupOwner, toCompare.mGroupOwner)
&& TextUtils.equals(mDisplayName, toCompare.mDisplayName)
&& TextUtils.equals(mCarrierName, toCompare.mCarrierName)
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a2efc96..fd9247c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -70,6 +70,7 @@
import android.os.WorkSource;
import android.provider.Settings.SettingNotFoundException;
import android.service.carrier.CarrierIdentifier;
+import android.service.carrier.CarrierService;
import android.sysprop.TelephonyProperties;
import android.telecom.CallScreeningService;
import android.telecom.InCallService;
@@ -131,6 +132,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -144,6 +147,7 @@
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+
/**
* Provides access to information about the telephony services on
* the device. Applications can use the methods in this class to
@@ -3503,7 +3507,8 @@
*/
private int getLogicalSlotIndex(int physicalSlotIndex) {
UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
- if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length) {
+ if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length
+ && slotInfos[physicalSlotIndex] != null) {
return slotInfos[physicalSlotIndex].getLogicalSlotIdx();
}
@@ -3939,8 +3944,8 @@
* <p>
* If the caller has carrier priviliges on any active subscription, then they have permission to
* get simple information like the card ID ({@link UiccCardInfo#getCardId()}), whether the card
- * is an eUICC ({@link UiccCardInfo#isEuicc()}), and the slot index where the card is inserted
- * ({@link UiccCardInfo#getSlotIndex()}).
+ * is an eUICC ({@link UiccCardInfo#isEuicc()}), and the physical slot index where the card is
+ * inserted ({@link UiccCardInfo#getPhysicalSlotIndex()}.
* <p>
* To get private information such as the EID ({@link UiccCardInfo#getEid()}) or ICCID
* ({@link UiccCardInfo#getIccId()}), the caller must have carrier priviliges on that specific
@@ -3984,7 +3989,7 @@
if (telephony == null) {
return null;
}
- return telephony.getUiccSlotsInfo();
+ return telephony.getUiccSlotsInfo(mContext.getOpPackageName());
} catch (RemoteException e) {
return null;
}
@@ -4017,8 +4022,13 @@
* size should be same as {@link #getUiccSlotsInfo()}.
* @return boolean Return true if the switch succeeds, false if the switch fails.
* @hide
+ * @deprecated {@link #setSimSlotMapping(Collection, Executor, Consumer)}
*/
+ // TODO: once integrating the HAL changes we can convert int[] to List<UiccSlotMapping> and
+ // converge API's in ITelephony.aidl and PhoneInterfaceManager
+
@SystemApi
+ @Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public boolean switchSlots(int[] physicalSlots) {
try {
@@ -4033,6 +4043,109 @@
}
/**
+ * @param slotMapping Logical to physical slot and port mapping.
+ * @return {@code true} if slotMapping is valid.
+ * @return {@code false} if slotMapping is invalid.
+ *
+ * slotMapping is invalid if there are different entries (physical slot + port) mapping to the
+ * same logical slot or if there are same {physical slot + port} mapping to the different
+ * logical slot
+ * @hide
+ */
+ private static boolean isSlotMappingValid(@NonNull Collection<UiccSlotMapping> slotMapping) {
+ // Grouping the collection by logicalSlotIndex, finding different entries mapping to the
+ // same logical slot
+ Map<Integer, List<UiccSlotMapping>> slotMappingInfo = slotMapping.stream().collect(
+ Collectors.groupingBy(UiccSlotMapping::getLogicalSlotIndex));
+ for (Map.Entry<Integer, List<UiccSlotMapping>> entry : slotMappingInfo.entrySet()) {
+ List<UiccSlotMapping> logicalSlotMap = entry.getValue();
+ if (logicalSlotMap.size() > 1) {
+ // duplicate logicalSlotIndex found
+ return false;
+ }
+ }
+
+ // Grouping the collection by physical slot and port, finding same entries mapping to the
+ // different logical slot
+ Map<List<Integer>, List<UiccSlotMapping>> slotMapInfos = slotMapping.stream().collect(
+ Collectors.groupingBy(
+ slot -> Arrays.asList(slot.getPhysicalSlotIndex(), slot.getPortIndex())));
+ for (Map.Entry<List<Integer>, List<UiccSlotMapping>> entry : slotMapInfos.entrySet()) {
+ List<UiccSlotMapping> portAndPhysicalSlotList = entry.getValue();
+ if (portAndPhysicalSlotList.size() > 1) {
+ // duplicate pair of portIndex and physicalSlotIndex found
+ return false;
+ }
+ }
+ return true;
+ }
+ /**
+ * Maps the logical slots to physical slots and ports. Mapping is specified from
+ * {@link UiccSlotMapping} which consist of both physical slot index and port index.
+ * Logical slot is the slot that is seen by modem. Physical slot is the actual physical slot.
+ * Port index is the index (enumerated value) for the associated port available on the SIM.
+ * Each physical slot can have multiple ports if multi-enabled profile(MEP) is supported.
+ *
+ * Example: no. of logical slots 1 and physical slots 2 do not support MEP, each physical slot
+ * has one port:
+ * The only logical slot (index 0) can be mapped to first physical slot (value 0), port(index
+ * 0) or
+ * second physical slot(value 1), port (index 0), while the other physical slot remains unmapped
+ * and inactive.
+ * slotMapping[0] = UiccSlotMapping{0 //logical slot, 0 //physical slot//, 0 //port//}
+ * slotMapping[0] = UiccSlotMapping{1 // logical slot, 1 //physical slot//, 0 //port//}
+ *
+ * Example no. of logical slots 2 and physical slots 2 supports MEP with 2 ports available:
+ * Each logical slot must be mapped to a port (physical slot and port combination).
+ * First logical slot (index 0) can be mapped to physical slot 1 and the second logical slot
+ * can be mapped to either port from physical slot 2.
+ *
+ * slotMapping[0] = UiccSlotMapping{0, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 0, 0} or
+ * slotMapping[0] = UiccSlotMapping{0, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 1, 1}
+ *
+ * or the other way around, the second logical slot(index 1) can be mapped to physical slot 1
+ * and the first logical slot can be mapped to either port from physical slot 2.
+ *
+ * slotMapping[1] = UiccSlotMapping{0, 0, 0} and slotMapping[0] = UiccSlotMapping{1, 0, 0} or
+ * slotMapping[1] = UiccSlotMapping{0, 0, 0} and slotMapping[0] = UiccSlotMapping{1, 1, 1}
+ *
+ * another possible mapping is each logical slot maps to each port of physical slot 2 and there
+ * is no active logical modem mapped to physical slot 1.
+ *
+ * slotMapping[0] = UiccSlotMapping{1, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 1, 1} or
+ * slotMapping[0] = UiccSlotMapping{1, 1, 1} and slotMapping[1] = UiccSlotMapping{1, 0, 0}
+ *
+ * @param slotMapping Logical to physical slot and port mapping.
+ * @throws IllegalStateException if telephony service is null or slot mapping was sent when the
+ * radio in middle of a silent restart or other invalid states to handle the command
+ * @throws IllegalArgumentException if the caller passes in an invalid collection of
+ * UiccSlotMapping like duplicate data, etc
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setSimSlotMapping(@NonNull Collection<UiccSlotMapping> slotMapping) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ if (isSlotMappingValid(slotMapping)) {
+ boolean result = telephony.setSimSlotMapping(new ArrayList(slotMapping));
+ if (!result) {
+ throw new IllegalStateException("setSimSlotMapping has failed");
+ }
+ } else {
+ throw new IllegalArgumentException("Duplicate UiccSlotMapping data found");
+ }
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Get the mapping from logical slots to physical slots. The key of the map is the logical slot
* id and the value is the physical slots id mapped to this logical slot id.
*
@@ -4049,7 +4162,7 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- int[] slotMappingArray = telephony.getSlotsMapping();
+ int[] slotMappingArray = telephony.getSlotsMapping(mContext.getOpPackageName());
for (int i = 0; i < slotMappingArray.length; i++) {
slotMapping.put(i, slotMappingArray[i]);
}
@@ -8320,12 +8433,58 @@
public NetworkScan requestNetworkScan(
NetworkScanRequest request, Executor executor,
TelephonyScanManager.NetworkScanCallback callback) {
- synchronized (this) {
+ return requestNetworkScan(false, request, executor, callback);
+ }
+
+ /**
+ * Request a network scan.
+ *
+ * This method is asynchronous, so the network scan results will be returned by callback.
+ * The returned NetworkScan will contain a callback method which can be used to stop the scan.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+ * app has carrier privileges (see {@link #hasCarrierPrivileges})
+ * and {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+ *
+ * If the system-wide location switch is off, apps may still call this API, with the
+ * following constraints:
+ * <ol>
+ * <li>The app must hold the {@code android.permission.NETWORK_SCAN} permission.</li>
+ * <li>The app must not supply any specific bands or channels to scan.</li>
+ * <li>The app must only specify MCC/MNC pairs that are
+ * associated to a SIM in the device.</li>
+ * <li>Returned results will have no meaningful info other than signal strength
+ * and MCC/MNC info.</li>
+ * </ol>
+ *
+ * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+ * location related information which will be sent if the caller already possess
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
+ * @param request Contains all the RAT with bands/channels that need to be scanned.
+ * @param executor The executor through which the callback should be invoked. Since the scan
+ * request may trigger multiple callbacks and they must be invoked in the same order as
+ * they are received by the platform, the user should provide an executor which executes
+ * tasks one at a time in serial order. For example AsyncTask.SERIAL_EXECUTOR.
+ * @param callback Returns network scan results or errors.
+ * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ })
+ public @Nullable NetworkScan requestNetworkScan(
+ boolean renounceFineLocationAccess, @NonNull NetworkScanRequest request,
+ @NonNull Executor executor,
+ @NonNull TelephonyScanManager.NetworkScanCallback callback) {
+ synchronized (sCacheLock) {
if (mTelephonyScanManager == null) {
mTelephonyScanManager = new TelephonyScanManager();
}
}
- return mTelephonyScanManager.requestNetworkScan(getSubId(), request, executor, callback,
+ return mTelephonyScanManager.requestNetworkScan(getSubId(), renounceFineLocationAccess,
+ request, executor, callback,
getOpPackageName(), getAttributionTag());
}
@@ -8803,7 +8962,8 @@
}
/**
- * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
+ * Set the preferred network type to global mode which includes NR, LTE, CDMA, EvDo
+ * and GSM/WCDMA.
*
* <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
@@ -8814,7 +8974,8 @@
}
/**
- * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
+ * Set the preferred network type to global mode which includes NR, LTE, CDMA, EvDo
+ * and GSM/WCDMA.
*
* <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
@@ -8822,7 +8983,7 @@
* @hide
*/
public boolean setPreferredNetworkTypeToGlobal(int subId) {
- return setPreferredNetworkType(subId, RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+ return setPreferredNetworkType(subId, RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
}
/**
@@ -9129,6 +9290,57 @@
return null;
}
+ /**
+ * Returns the package name that provides the {@link CarrierService} implementation for the
+ * current subscription, or {@code null} if no package with carrier privileges declares one.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, then the provided
+ * subscription ID is used. Otherwise, the default subscription ID will be used.
+ *
+ * @return The system-selected package that provides the {@link CarrierService} implementation
+ * for the current subscription, or {@code null} if none is resolved
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @Nullable String getCarrierServicePackageName() {
+ // TODO(b/205736323) plumb this through to CarrierPrivilegesTracker, which will cache the
+ // value instead of re-querying every time.
+ List<String> carrierServicePackages =
+ getCarrierPackageNamesForIntent(
+ new Intent(CarrierService.CARRIER_SERVICE_INTERFACE));
+ if (carrierServicePackages != null && !carrierServicePackages.isEmpty()) {
+ return carrierServicePackages.get(0);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the package name that provides the {@link CarrierService} implementation for the
+ * specified {@code logicalSlotIndex}, or {@code null} if no package with carrier privileges
+ * declares one.
+ *
+ * @param logicalSlotIndex The slot index to fetch the {@link CarrierService} package for
+ * @return The system-selected package that provides the {@link CarrierService} implementation
+ * for the slot, or {@code null} if none is resolved
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @Nullable String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
+ // TODO(b/205736323) plumb this through to CarrierPrivilegesTracker, which will cache the
+ // value instead of re-querying every time.
+ List<String> carrierServicePackages =
+ getCarrierPackageNamesForIntentAndPhone(
+ new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), logicalSlotIndex);
+ if (carrierServicePackages != null && !carrierServicePackages.isEmpty()) {
+ return carrierServicePackages.get(0);
+ }
+ return null;
+ }
+
/** @hide */
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public List<String> getPackagesWithCarrierPrivileges() {
@@ -11384,7 +11596,6 @@
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
* and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
- *
* May return {@code null} when the subscription is inactive or when there was an error
* communicating with the phone process.
*/
@@ -11394,7 +11605,72 @@
Manifest.permission.ACCESS_COARSE_LOCATION
})
public @Nullable ServiceState getServiceState() {
- return getServiceStateForSubscriber(getSubId());
+ return getServiceState(false, false);
+ }
+
+ /**
+ * Returns the current {@link ServiceState} information.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+ *
+ * If you want continuous updates of service state info, register a {@link PhoneStateListener}
+ * via {@link #listen} with the {@link PhoneStateListener#LISTEN_SERVICE_STATE} event.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
+ * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+ * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+ * location related information which will be sent if the caller already possess
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
+ * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
+ * receive location related information which will be sent if the caller already possess
+ * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+ * May return {@code null} when the subscription is inactive or when there was an error
+ * communicating with the phone process.
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @RequiresPermission(allOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ })
+ public @Nullable ServiceState getServiceState(boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess) {
+ return getServiceStateForSubscriber(getSubId(), renounceFineLocationAccess,
+ renounceCoarseLocationAccess);
+ }
+
+ /**
+ * Returns the service state information on specified subscription. Callers require
+ * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
+ *
+ * May return {@code null} when the subscription is inactive or when there was an error
+ * communicating with the phone process.
+ * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+ * location related information which will be sent if the caller already possess
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
+ * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
+ * receive location related information which will be sent if the caller already possess
+ * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+ */
+ private ServiceState getServiceStateForSubscriber(int subId,
+ boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.getServiceStateForSubscriber(subId, renounceFineLocationAccess,
+ renounceCoarseLocationAccess,
+ getOpPackageName(), getAttributionTag());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
+ } catch (NullPointerException e) {
+ AnomalyReporter.reportAnomaly(
+ UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ "getServiceStateForSubscriber " + subId + " NPE");
+ }
+ return null;
}
/**
@@ -11407,20 +11683,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public ServiceState getServiceStateForSubscriber(int subId) {
- try {
- ITelephony service = getITelephony();
- if (service != null) {
- return service.getServiceStateForSubscriber(subId, getOpPackageName(),
- getAttributionTag());
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
- } catch (NullPointerException e) {
- AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
- "getServiceStateForSubscriber " + subId + " NPE");
- }
- return null;
+ return getServiceStateForSubscriber(getSubId(), false, false);
}
/**
@@ -14242,7 +14505,8 @@
if (callForwardingInfo.getNumber() == null) {
throw new IllegalArgumentException("callForwarding number is null");
}
- if (callForwardingInfo.getTimeoutSeconds() <= 0) {
+ if (callForwardingReason == CallForwardingInfo.REASON_NO_REPLY
+ && callForwardingInfo.getTimeoutSeconds() <= 0) {
throw new IllegalArgumentException("callForwarding timeout isn't positive");
}
}
@@ -15270,7 +15534,49 @@
*/
public void registerTelephonyCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull TelephonyCallback callback) {
+ registerTelephonyCallback(false, false, executor, callback);
+ }
+ /**
+ * Registers a callback object to receive notification of changes in specified telephony states.
+ * <p>
+ * To register a callback, pass a {@link TelephonyCallback} which implements
+ * interfaces of events. For example,
+ * FakeServiceStateCallback extends {@link TelephonyCallback} implements
+ * {@link TelephonyCallback.ServiceStateListener}.
+ *
+ * At registration, and when a specified telephony state changes, the telephony manager invokes
+ * the appropriate callback method on the callback object and passes the current (updated)
+ * values.
+ * <p>
+ *
+ * If this TelephonyManager object has been created with {@link #createForSubscriptionId},
+ * applies to the given subId. Otherwise, applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}. To register events for multiple
+ * subIds, pass a separate callback object to each TelephonyManager object created with
+ * {@link #createForSubscriptionId}.
+ *
+ * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
+ * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
+ * {@link SecurityException} will be thrown otherwise.
+ *
+ * This API should be used sparingly -- large numbers of callbacks will cause system
+ * instability. If a process has registered too many callbacks without unregistering them, it
+ * may encounter an {@link IllegalStateException} when trying to register more callbacks.
+ *
+ * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+ * location related information which will be sent if the caller already possess
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permissions.
+ * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
+ * receive location related information which will be sent if the caller already possess
+ * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The {@link TelephonyCallback} object to register.
+ */
+ public void registerTelephonyCallback(boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull TelephonyCallback callback) {
if (mContext == null) {
throw new IllegalStateException("telephony service is null.");
}
@@ -15281,7 +15587,8 @@
mTelephonyRegistryMgr = (TelephonyRegistryManager)
mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
if (mTelephonyRegistryMgr != null) {
- mTelephonyRegistryMgr.registerTelephonyCallback(executor, mSubId, getOpPackageName(),
+ mTelephonyRegistryMgr.registerTelephonyCallback(renounceFineLocationAccess,
+ renounceCoarseLocationAccess, executor, mSubId, getOpPackageName(),
getAttributionTag(), callback, getITelephony() != null);
} else {
throw new IllegalStateException("telephony service is null.");
@@ -15832,4 +16139,76 @@
}
return null;
}
+
+ /**
+ * Callback to listen for when the set of packages with carrier privileges for a SIM changes.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface CarrierPrivilegesListener {
+ /**
+ * Called when the set of packages with carrier privileges has changed.
+ *
+ * <p>Of note, this callback will <b>not</b> be fired if a carrier triggers a SIM profile
+ * switch and the same set of packages remains privileged after the switch.
+ *
+ * <p>At registration, the callback will receive the current set of privileged packages.
+ *
+ * @param privilegedPackageNames The updated set of package names that have carrier
+ * privileges
+ * @param privilegedUids The updated set of UIDs that have carrier privileges
+ */
+ void onCarrierPrivilegesChanged(
+ @NonNull List<String> privilegedPackageNames, @NonNull int[] privilegedUids);
+ }
+
+ /**
+ * Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to
+ * receive callbacks when the set of packages with carrier privileges changes. The callback will
+ * immediately be called with the latest state.
+ *
+ * @param logicalSlotIndex The SIM slot to listen on
+ * @param executor The executor where {@code listener} will be invoked
+ * @param listener The callback to register
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void addCarrierPrivilegesListener(
+ int logicalSlotIndex,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CarrierPrivilegesListener listener) {
+ if (mContext == null) {
+ throw new IllegalStateException("Telephony service is null");
+ } else if (executor == null || listener == null) {
+ throw new IllegalArgumentException(
+ "CarrierPrivilegesListener and executor must be non-null");
+ }
+ mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+ if (mTelephonyRegistryMgr == null) {
+ throw new IllegalStateException("Telephony registry service is null");
+ }
+ mTelephonyRegistryMgr.addCarrierPrivilegesListener(logicalSlotIndex, executor, listener);
+ }
+
+ /**
+ * Unregisters an existing {@link CarrierPrivilegesListener}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) {
+ if (mContext == null) {
+ throw new IllegalStateException("Telephony service is null");
+ } else if (listener == null) {
+ throw new IllegalArgumentException("CarrierPrivilegesListener must be non-null");
+ }
+ mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+ if (mTelephonyRegistryMgr == null) {
+ throw new IllegalStateException("Telephony registry service is null");
+ }
+ mTelephonyRegistryMgr.removeCarrierPrivilegesListener(listener);
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 9572154..122662d 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -228,7 +228,9 @@
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
* Or the calling app has carrier privileges. @see #hasCarrierPrivileges
- *
+ * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+ * location related information which will be sent if the caller already possess
+ * {@link android.Manifest.permission.ACCESS_FINE_LOCATION} and do not renounce the permission
* @param request Contains all the RAT with bands/channels that need to be scanned.
* @param callback Returns network scan results or errors.
* @param callingPackage The package name of the caller
@@ -237,6 +239,7 @@
* @hide
*/
public NetworkScan requestNetworkScan(int subId,
+ boolean renounceFineLocationAccess,
NetworkScanRequest request, Executor executor, NetworkScanCallback callback,
String callingPackage, @Nullable String callingFeatureId) {
try {
@@ -252,7 +255,8 @@
// the record to the ScanInfo cache.
synchronized (mScanInfo) {
int scanId = telephony.requestNetworkScan(
- subId, request, mMessenger, new Binder(), callingPackage,
+ subId, renounceFineLocationAccess, request, mMessenger,
+ new Binder(), callingPackage,
callingFeatureId);
if (scanId == INVALID_SCAN_ID) {
Rlog.e(TAG, "Failed to initiate network scan");
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index b4389203..7dfe450 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -20,21 +20,27 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
* The UiccCardInfo represents information about a currently inserted UICC or embedded eUICC.
*/
public final class UiccCardInfo implements Parcelable {
-
private final boolean mIsEuicc;
private final int mCardId;
private final String mEid;
private final String mIccId;
- private final int mSlotIndex;
+ private final int mPhysicalSlotIndex;
private final boolean mIsRemovable;
+ private final boolean mIsMultipleEnabledProfilesSupported;
+ private final List<UiccPortInfo> mPortList;
+ private boolean mIccIdAccessRestricted = false;
- public static final @android.annotation.NonNull Creator<UiccCardInfo> CREATOR = new Creator<UiccCardInfo>() {
+ public static final @NonNull Creator<UiccCardInfo> CREATOR = new Creator<UiccCardInfo>() {
@Override
public UiccCardInfo createFromParcel(Parcel in) {
return new UiccCardInfo(in);
@@ -47,22 +53,29 @@
};
private UiccCardInfo(Parcel in) {
- mIsEuicc = in.readByte() != 0;
+ mIsEuicc = in.readBoolean();
mCardId = in.readInt();
- mEid = in.readString();
- mIccId = in.readString();
- mSlotIndex = in.readInt();
- mIsRemovable = in.readByte() != 0;
+ mEid = in.readString8();
+ mIccId = in.readString8();
+ mPhysicalSlotIndex = in.readInt();
+ mIsRemovable = in.readBoolean();
+ mIsMultipleEnabledProfilesSupported = in.readBoolean();
+ mPortList = new ArrayList<UiccPortInfo>();
+ in.readTypedList(mPortList, UiccPortInfo.CREATOR);
+ mIccIdAccessRestricted = in.readBoolean();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeByte((byte) (mIsEuicc ? 1 : 0));
+ dest.writeBoolean(mIsEuicc);
dest.writeInt(mCardId);
- dest.writeString(mEid);
- dest.writeString(mIccId);
- dest.writeInt(mSlotIndex);
- dest.writeByte((byte) (mIsRemovable ? 1 : 0));
+ dest.writeString8(mEid);
+ dest.writeString8(mIccId);
+ dest.writeInt(mPhysicalSlotIndex);
+ dest.writeBoolean(mIsRemovable);
+ dest.writeBoolean(mIsMultipleEnabledProfilesSupported);
+ dest.writeTypedList(mPortList, flags);
+ dest.writeBoolean(mIccIdAccessRestricted);
}
@Override
@@ -71,20 +84,34 @@
}
/**
+ * Construct a UiccCardInfo.
+ *
+ * @param isEuicc is a flag to check is eUICC or not
+ * @param cardId is unique ID used to identify a UiccCard.
+ * @param eid is unique eUICC Identifier
+ * @param physicalSlotIndex is unique index referring to a physical SIM slot.
+ * @param isRemovable is a flag to check is removable or embedded
+ * @param isMultipleEnabledProfilesSupported is a flag to check is MEP enabled or not
+ * @param portList has the information regarding port, ICCID and its active status
+ *
* @hide
*/
- public UiccCardInfo(boolean isEuicc, int cardId, String eid, String iccId, int slotIndex,
- boolean isRemovable) {
+ public UiccCardInfo(boolean isEuicc, int cardId, String eid, int physicalSlotIndex,
+ boolean isRemovable, boolean isMultipleEnabledProfilesSupported,
+ @NonNull List<UiccPortInfo> portList) {
this.mIsEuicc = isEuicc;
this.mCardId = cardId;
this.mEid = eid;
- this.mIccId = iccId;
- this.mSlotIndex = slotIndex;
+ this.mIccId = null;
+ this.mPhysicalSlotIndex = physicalSlotIndex;
this.mIsRemovable = isRemovable;
+ this.mIsMultipleEnabledProfilesSupported = isMultipleEnabledProfilesSupported;
+ this.mPortList = portList;
}
/**
* Return whether the UICC is an eUICC.
+ *
* @return true if the UICC is an eUICC.
*/
public boolean isEuicc() {
@@ -119,40 +146,83 @@
* <p>
* Note that this field may be omitted if the caller does not have the correct permissions
* (see {@link TelephonyManager#getUiccCardsInfo()}).
+ *
+ * @deprecated with support for MEP(multiple enabled profile), a SIM card can have more than one
+ * ICCID active at the same time.Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID.
+ * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()}
+ *
+ * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond.
*/
@Nullable
+ @Deprecated
public String getIccId() {
- return mIccId;
+ if (mIccIdAccessRestricted) {
+ throw new UnsupportedOperationException("getIccId from UiccPortInfo");
+ }
+ //always return ICCID from first port.
+ return getPorts().stream().findFirst().get().getIccId();
}
/**
* Gets the slot index for the slot that the UICC is currently inserted in.
+ *
+ * @deprecated use {@link #getPhysicalSlotIndex()}
*/
+ @Deprecated
public int getSlotIndex() {
- return mSlotIndex;
+ return mPhysicalSlotIndex;
}
/**
- * Returns a copy of the UiccCardinfo with the EID and ICCID set to null. These values are
- * generally private and require carrier privileges to view.
- *
- * @hide
+ * Gets the physical slot index for the slot that the UICC is currently inserted in.
*/
- @NonNull
- public UiccCardInfo getUnprivileged() {
- return new UiccCardInfo(mIsEuicc, mCardId, null, null, mSlotIndex, mIsRemovable);
+ public int getPhysicalSlotIndex() {
+ return mPhysicalSlotIndex;
}
/**
* Return whether the UICC or eUICC is removable.
* <p>
* UICCs are generally removable, but eUICCs may be removable or built in to the device.
+ *
* @return true if the UICC or eUICC is removable
*/
public boolean isRemovable() {
return mIsRemovable;
}
+ /*
+ * Whether the UICC card supports multiple enable profile(MEP)
+ * UICCs are generally MEP disabled, there can be only one active profile on the physical
+ * sim card.
+ *
+ * @return {@code true} if the eUICC is supporting multiple enabled profile(MEP).
+ */
+ public boolean isMultipleEnabledProfilesSupported() {
+ return mIsMultipleEnabledProfilesSupported;
+ }
+
+ /**
+ * Get information regarding port, ICCID and its active status.
+ *
+ * @return Collection of {@link UiccPortInfo}
+ */
+ public @NonNull Collection<UiccPortInfo> getPorts() {
+ return Collections.unmodifiableList(mPortList);
+ }
+
+ /**
+ * if the flag is set to {@code true} the calling app is not allowed to access deprecated
+ * {@link #getIccId()}
+ * @param iccIdAccessRestricted is the flag to check if app is allowed to access ICCID
+ *
+ * @hide
+ */
+ public void setIccIdAccessRestricted(boolean iccIdAccessRestricted) {
+ this.mIccIdAccessRestricted = iccIdAccessRestricted;
+ }
+
+
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -167,13 +237,16 @@
&& (mCardId == that.mCardId)
&& (Objects.equals(mEid, that.mEid))
&& (Objects.equals(mIccId, that.mIccId))
- && (mSlotIndex == that.mSlotIndex)
- && (mIsRemovable == that.mIsRemovable));
+ && (mPhysicalSlotIndex == that.mPhysicalSlotIndex)
+ && (mIsRemovable == that.mIsRemovable)
+ && (mIsMultipleEnabledProfilesSupported == that.mIsMultipleEnabledProfilesSupported)
+ && (Objects.equals(mPortList, that.mPortList)));
}
@Override
public int hashCode() {
- return Objects.hash(mIsEuicc, mCardId, mEid, mIccId, mSlotIndex, mIsRemovable);
+ return Objects.hash(mIsEuicc, mCardId, mEid, mIccId, mPhysicalSlotIndex, mIsRemovable,
+ mIsMultipleEnabledProfilesSupported, mPortList);
}
@Override
@@ -185,11 +258,17 @@
+ ", mEid="
+ mEid
+ ", mIccId="
- + mIccId
- + ", mSlotIndex="
- + mSlotIndex
+ + SubscriptionInfo.givePrintableIccid(mIccId)
+ + ", mPhysicalSlotIndex="
+ + mPhysicalSlotIndex
+ ", mIsRemovable="
+ mIsRemovable
+ + ", mIsMultipleEnabledProfilesSupported="
+ + mIsMultipleEnabledProfilesSupported
+ + ", mPortList="
+ + mPortList
+ + ", mIccIdAccessRestricted="
+ + mIccIdAccessRestricted
+ ")";
}
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/UiccPortInfo.aidl b/telephony/java/android/telephony/UiccPortInfo.aidl
new file mode 100644
index 0000000..7fff4ba
--- /dev/null
+++ b/telephony/java/android/telephony/UiccPortInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 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 android.telephony;
+
+parcelable UiccPortInfo;
diff --git a/telephony/java/android/telephony/UiccPortInfo.java b/telephony/java/android/telephony/UiccPortInfo.java
new file mode 100644
index 0000000..d1838c0
--- /dev/null
+++ b/telephony/java/android/telephony/UiccPortInfo.java
@@ -0,0 +1,173 @@
+/*
+ * 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 android.telephony;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * UiccPortInfo class represents information about a single port contained on {@link UiccCardInfo}.
+ * Per GSMA SGP.22 V3.0, a port is a logical entity to which an active UICC profile can be bound on
+ * a UICC card. If UICC supports 2 ports, then the port index is numbered 0,1.
+ * Each port index is unique within an UICC, but not necessarily unique across UICC’s.
+ * For UICC's does not support MEP(Multi-enabled profile), just return the default port index 0.
+ */
+public final class UiccPortInfo implements Parcelable{
+ private final String mIccId;
+ private final int mPortIndex;
+ private final int mLogicalSlotIndex;
+ private final boolean mIsActive;
+
+ /**
+ * A redacted String if caller does not have permission to read ICCID.
+ */
+ public static final String ICCID_REDACTED = "FFFFFFFFFFFFFFFFFFFF";
+
+ public static final @NonNull Creator<UiccPortInfo> CREATOR =
+ new Creator<UiccPortInfo>() {
+ @Override
+ public UiccPortInfo createFromParcel(Parcel in) {
+ return new UiccPortInfo(in);
+ }
+ @Override
+ public UiccPortInfo[] newArray(int size) {
+ return new UiccPortInfo[size];
+ }
+ };
+
+ private UiccPortInfo(Parcel in) {
+ mIccId = in.readString8();
+ mPortIndex = in.readInt();
+ mLogicalSlotIndex = in.readInt();
+ mIsActive = in.readBoolean();
+ }
+
+ @Override
+ public void writeToParcel(@Nullable Parcel dest, int flags) {
+ dest.writeString8(mIccId);
+ dest.writeInt(mPortIndex);
+ dest.writeInt(mLogicalSlotIndex);
+ dest.writeBoolean(mIsActive);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Construct a UiccPortInfo.
+ *
+ * @param iccId The ICCID of the profile.
+ * @param portIndex The port index is an enumeration of the ports available on the UICC.
+ * @param logicalSlotIndex is unique index referring to a logical SIM slot.
+ * @param isActive is flag to check if port was tied to a modem stack.
+ *
+ * @hide
+ */
+ public UiccPortInfo(String iccId, int portIndex, int logicalSlotIndex, boolean isActive) {
+ this.mIccId = iccId;
+ this.mPortIndex = portIndex;
+ this.mLogicalSlotIndex = logicalSlotIndex;
+ this.mIsActive = isActive;
+ }
+
+ /**
+ * Get the ICCID of the profile associated with this port.
+ * If this port is not {@link #isActive()}, returns {@code null}.
+ * If the caller does not have access to the ICCID for this port, it will be redacted and
+ * {@link #ICCID_REDACTED} will be returned.
+ */
+ public @Nullable String getIccId() {
+ return mIccId;
+ }
+
+ /**
+ * The port index is an enumeration of the ports available on the UICC.
+ * Example: if eUICC1 supports 2 ports, then the port index is numbered 0,1.
+ * Each port index is unique within an UICC, but not necessarily unique across UICC’s.
+ * For UICC's does not support MEP(Multi-enabled profile), just return the default port index 0.
+ */
+ @IntRange(from = 0)
+ public int getPortIndex() {
+ return mPortIndex;
+ }
+
+ /**
+ * @return {@code true} if port was tied to a modem stack.
+ */
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ /**
+ * Gets logical slot index for the slot that the UICC is currently attached.
+ * Logical slot index or ID: unique index referring to a logical SIM slot.
+ * Logical slot IDs start at 0 and go up depending on the number of supported active slots on
+ * a device.
+ * For example, a dual-SIM device typically has slot 0 and slot 1.
+ * If a device has multiple physical slots but only supports one active slot,
+ * it will have only the logical slot ID 0.
+ *
+ * @return the logical slot index for UICC port, if there is no logical slot index it returns
+ * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX}
+ */
+ @IntRange(from = 0)
+ public int getLogicalSlotIndex() {
+ return mLogicalSlotIndex;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ UiccPortInfo that = (UiccPortInfo) obj;
+ return (Objects.equals(mIccId, that.mIccId))
+ && (mPortIndex == that.mPortIndex)
+ && (mLogicalSlotIndex == that.mLogicalSlotIndex)
+ && (mIsActive == that.mIsActive);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIccId, mPortIndex, mLogicalSlotIndex, mIsActive);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "UiccPortInfo (isActive="
+ + mIsActive
+ + ", iccId="
+ + SubscriptionInfo.givePrintableIccid(mIccId)
+ + ", portIndex="
+ + mPortIndex
+ + ", mLogicalSlotIndex="
+ + mLogicalSlotIndex
+ + ")";
+ }
+}
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index a0e949a..2b1c8c8 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -24,6 +24,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
@@ -64,8 +68,10 @@
private final int mLogicalSlotIdx;
private final boolean mIsExtendedApduSupported;
private final boolean mIsRemovable;
+ private final List<UiccPortInfo> mPortList;
+ private boolean mLogicalSlotAccessRestricted = false;
- public static final @android.annotation.NonNull Creator<UiccSlotInfo> CREATOR = new Creator<UiccSlotInfo>() {
+ public static final @NonNull Creator<UiccSlotInfo> CREATOR = new Creator<UiccSlotInfo>() {
@Override
public UiccSlotInfo createFromParcel(Parcel in) {
return new UiccSlotInfo(in);
@@ -78,24 +84,29 @@
};
private UiccSlotInfo(Parcel in) {
- mIsActive = in.readByte() != 0;
- mIsEuicc = in.readByte() != 0;
- mCardId = in.readString();
+ mIsActive = in.readBoolean();
+ mIsEuicc = in.readBoolean();
+ mCardId = in.readString8();
mCardStateInfo = in.readInt();
mLogicalSlotIdx = in.readInt();
- mIsExtendedApduSupported = in.readByte() != 0;
- mIsRemovable = in.readByte() != 0;
+ mIsExtendedApduSupported = in.readBoolean();
+ mIsRemovable = in.readBoolean();
+ mPortList = new ArrayList<UiccPortInfo>();
+ in.readTypedList(mPortList, UiccPortInfo.CREATOR);
+ mLogicalSlotAccessRestricted = in.readBoolean();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeByte((byte) (mIsActive ? 1 : 0));
- dest.writeByte((byte) (mIsEuicc ? 1 : 0));
- dest.writeString(mCardId);
+ dest.writeBoolean(mIsActive);
+ dest.writeBoolean(mIsEuicc);
+ dest.writeString8(mCardId);
dest.writeInt(mCardStateInfo);
dest.writeInt(mLogicalSlotIdx);
- dest.writeByte((byte) (mIsExtendedApduSupported ? 1 : 0));
- dest.writeByte((byte) (mIsRemovable ? 1 : 0));
+ dest.writeBoolean(mIsExtendedApduSupported);
+ dest.writeBoolean(mIsRemovable);
+ dest.writeTypedList(mPortList, flags);
+ dest.writeBoolean(mLogicalSlotAccessRestricted);
}
@Override
@@ -117,25 +128,42 @@
this.mLogicalSlotIdx = logicalSlotIdx;
this.mIsExtendedApduSupported = isExtendedApduSupported;
this.mIsRemovable = false;
+ this.mPortList = null;
}
/**
+ * Construct a UiccSlotInfo.
* @hide
*/
- public UiccSlotInfo(boolean isActive, boolean isEuicc, String cardId,
- @CardStateInfo int cardStateInfo, int logicalSlotIdx, boolean isExtendedApduSupported,
- boolean isRemovable) {
- this.mIsActive = isActive;
+ public UiccSlotInfo(boolean isEuicc, String cardId,
+ @CardStateInfo int cardStateInfo, boolean isExtendedApduSupported,
+ boolean isRemovable, @NonNull List<UiccPortInfo> portList) {
+ this.mIsActive = portList.get(0).isActive();
this.mIsEuicc = isEuicc;
this.mCardId = cardId;
this.mCardStateInfo = cardStateInfo;
- this.mLogicalSlotIdx = logicalSlotIdx;
+ this.mLogicalSlotIdx = portList.get(0).getLogicalSlotIndex();
this.mIsExtendedApduSupported = isExtendedApduSupported;
this.mIsRemovable = isRemovable;
+ this.mPortList = portList;
}
+ /**
+ * @deprecated There is no longer isActive state for each slot because ports belonging
+ * to the physical slot could have different states
+ * we instead use {@link UiccPortInfo#isActive()}
+ * To get UiccPortInfo use {@link UiccSlotInfo#getPorts()}
+ *
+ * @return {@code true} if status is active.
+ * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond.
+ */
+ @Deprecated
public boolean getIsActive() {
- return mIsActive;
+ if (mLogicalSlotAccessRestricted) {
+ throw new UnsupportedOperationException("get port status from UiccPortInfo");
+ }
+ //always return status from first port.
+ return getPorts().stream().findFirst().get().isActive();
}
public boolean getIsEuicc() {
@@ -159,8 +187,21 @@
return mCardStateInfo;
}
+ /**
+ * @deprecated There is no longer getLogicalSlotIndex
+ * There is no longer getLogicalSlotIdx as each port belonging to this physical slot could have
+ * different logical slot index. Use {@link UiccPortInfo#getLogicalSlotIndex()} instead
+ *
+ * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond.
+ */
+ @Deprecated
public int getLogicalSlotIdx() {
- return mLogicalSlotIdx;
+ if (mLogicalSlotAccessRestricted) {
+ throw new UnsupportedOperationException("get logical slot index from UiccPortInfo");
+ }
+ //always return logical slot index from first port.
+ //portList always have at least one element.
+ return getPorts().stream().findFirst().get().getLogicalSlotIndex();
}
/**
@@ -170,16 +211,37 @@
return mIsExtendedApduSupported;
}
- /**
+ /**
* Return whether the UICC slot is for a removable UICC.
* <p>
* UICCs are generally removable, but eUICCs may be removable or built in to the device.
+ *
* @return true if the slot is for removable UICCs
*/
public boolean isRemovable() {
return mIsRemovable;
}
+ /**
+ * Get Information regarding port, iccid and its active status.
+ *
+ * @return Collection of {@link UiccPortInfo}
+ */
+ public @NonNull Collection<UiccPortInfo> getPorts() {
+ return Collections.unmodifiableList(mPortList);
+ }
+
+ /**
+ * Set the flag to check compatibility of the calling app's target SDK is T and beyond.
+ *
+ * @param logicalSlotAccessRestricted is the flag to check compatibility.
+ *
+ * @hide
+ */
+ public void setLogicalSlotAccessRestricted(boolean logicalSlotAccessRestricted) {
+ this.mLogicalSlotAccessRestricted = logicalSlotAccessRestricted;
+ }
+
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
@@ -196,20 +258,14 @@
&& (mCardStateInfo == that.mCardStateInfo)
&& (mLogicalSlotIdx == that.mLogicalSlotIdx)
&& (mIsExtendedApduSupported == that.mIsExtendedApduSupported)
- && (mIsRemovable == that.mIsRemovable);
+ && (mIsRemovable == that.mIsRemovable)
+ && (Objects.equals(mPortList, that.mPortList));
}
@Override
public int hashCode() {
- int result = 1;
- result = 31 * result + (mIsActive ? 1 : 0);
- result = 31 * result + (mIsEuicc ? 1 : 0);
- result = 31 * result + Objects.hashCode(mCardId);
- result = 31 * result + mCardStateInfo;
- result = 31 * result + mLogicalSlotIdx;
- result = 31 * result + (mIsExtendedApduSupported ? 1 : 0);
- result = 31 * result + (mIsRemovable ? 1 : 0);
- return result;
+ return Objects.hash(mIsActive, mIsEuicc, mCardId, mCardStateInfo, mLogicalSlotIdx,
+ mIsExtendedApduSupported, mIsRemovable, mPortList);
}
@NonNull
@@ -229,6 +285,10 @@
+ mIsExtendedApduSupported
+ ", mIsRemovable="
+ mIsRemovable
+ + ", mPortList="
+ + mPortList
+ + ", mLogicalSlotAccessRestricted="
+ + mLogicalSlotAccessRestricted
+ ")";
}
}
diff --git a/telephony/java/android/telephony/UiccSlotMapping.aidl b/telephony/java/android/telephony/UiccSlotMapping.aidl
new file mode 100644
index 0000000..3b19499
--- /dev/null
+++ b/telephony/java/android/telephony/UiccSlotMapping.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 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 android.telephony;
+
+parcelable UiccSlotMapping;
diff --git a/telephony/java/android/telephony/UiccSlotMapping.java b/telephony/java/android/telephony/UiccSlotMapping.java
new file mode 100644
index 0000000..87e7acd
--- /dev/null
+++ b/telephony/java/android/telephony/UiccSlotMapping.java
@@ -0,0 +1,168 @@
+/*
+ * 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 android.telephony;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * <p>Provides information for a SIM slot mapping, which establishes a unique mapping between a
+ * logical SIM slot and a physical SIM slot and port index. A logical SIM slot represents a
+ * potentially active SIM slot, where a physical SIM slot and port index represent a hardware SIM
+ * slot and port (capable of having an active profile) which can be mapped to a logical sim slot.
+ * <p>It contains the following parameters:
+ * <ul>
+ * <li>Port index: unique index referring to a port belonging to the physical SIM slot.
+ * If the SIM does not support multiple enabled profiles, the port index is default index 0.</li>
+ * <li>Physical slot index: unique index referring to a physical SIM slot. Physical slot IDs start
+ * at 0 and go up depending on the number of physical slots on the device.
+ * This differs from the number of logical slots a device has, which corresponds to the number of
+ * active slots a device is capable of using. For example, a device which switches between dual-SIM
+ * and single-SIM mode may always have two physical slots, but in single-SIM mode it will have only
+ * one logical slot.</li>
+ * <li>Logical slot index: unique index referring to a logical SIM slot, Logical slot IDs start at 0
+ * and go up depending on the number of supported active slots on a device.
+ * For example, a dual-SIM device typically has slot 0 and slot 1. If a device has multiple physical
+ * slots but only supports one active slot, it will have only the logical slot ID 0</li>
+ * </ul>
+ *
+ * <p> This configurations tells a specific logical slot is mapped to a port from an actual physical
+ * sim slot @see <a href="https://developer.android.com/guide/topics/connectivity/telecom/telephony-ids">the Android Developer Site</a>
+ * for more information.
+ * @hide
+ */
+@SystemApi
+public final class UiccSlotMapping implements Parcelable {
+ private final int mPortIndex;
+ private final int mPhysicalSlotIndex;
+ private final int mLogicalSlotIndex;
+
+ public static final @NonNull Creator<UiccSlotMapping> CREATOR =
+ new Creator<UiccSlotMapping>() {
+ @Override
+ public UiccSlotMapping createFromParcel(Parcel in) {
+ return new UiccSlotMapping(in);
+ }
+
+ @Override
+ public UiccSlotMapping[] newArray(int size) {
+ return new UiccSlotMapping[size];
+ }
+ };
+
+ private UiccSlotMapping(Parcel in) {
+ mPortIndex = in.readInt();
+ mPhysicalSlotIndex = in.readInt();
+ mLogicalSlotIndex = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@Nullable Parcel dest, int flags) {
+ dest.writeInt(mPortIndex);
+ dest.writeInt(mPhysicalSlotIndex);
+ dest.writeInt(mLogicalSlotIndex);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ *
+ * @param portIndex The port index is an enumeration of the ports available on the UICC.
+ * @param physicalSlotIndex is unique index referring to a physical SIM slot.
+ * @param logicalSlotIndex is unique index referring to a logical SIM slot.
+ *
+ * @hide
+ */
+ public UiccSlotMapping(int portIndex, int physicalSlotIndex, int logicalSlotIndex) {
+ this.mPortIndex = portIndex;
+ this.mPhysicalSlotIndex = physicalSlotIndex;
+ this.mLogicalSlotIndex = logicalSlotIndex;
+ }
+
+ /**
+ * Port index is the unique index referring to a port belonging to the physical SIM slot.
+ * If the SIM does not support multiple enabled profiles, the port index is default index 0.
+ *
+ * @return port index.
+ */
+ @IntRange(from = 0)
+ public int getPortIndex() {
+ return mPortIndex;
+ }
+
+ /**
+ * Gets the physical slot index for the slot that the UICC is currently inserted in.
+ *
+ * @return physical slot index which is the index of actual physical UICC slot.
+ */
+ @IntRange(from = 0)
+ public int getPhysicalSlotIndex() {
+ return mPhysicalSlotIndex;
+ }
+
+ /**
+ * Gets logical slot index for the slot that the UICC is currently attached.
+ * Logical slot index is the unique index referring to a logical slot(logical modem stack).
+ *
+ * @return logical slot index;
+ */
+ @IntRange(from = 0)
+ public int getLogicalSlotIndex() {
+ return mLogicalSlotIndex;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ UiccSlotMapping that = (UiccSlotMapping) obj;
+ return (mPortIndex == that.mPortIndex)
+ && (mPhysicalSlotIndex == that.mPhysicalSlotIndex)
+ && (mLogicalSlotIndex == that.mLogicalSlotIndex);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPortIndex, mPhysicalSlotIndex, mLogicalSlotIndex);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "UiccSlotMapping (mPortIndex="
+ + mPortIndex
+ + ", mPhysicalSlotIndex="
+ + mPhysicalSlotIndex
+ + ", mLogicalSlotIndex="
+ + mLogicalSlotIndex
+ + ")";
+ }
+}
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 1ef04be..d6dce25 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -328,8 +328,6 @@
@SystemApi
public static final String TYPE_XCAP_STRING = "xcap";
-
-
/**
* APN type for Virtual SIM service.
*
@@ -506,27 +504,21 @@
private final int mRoamingProtocol;
private final int mMtuV4;
private final int mMtuV6;
-
private final boolean mCarrierEnabled;
-
- private final int mNetworkTypeBitmask;
-
+ private final @TelephonyManager.NetworkTypeBitMask int mNetworkTypeBitmask;
+ private final @TelephonyManager.NetworkTypeBitMask long mLingeringNetworkTypeBitmask;
private final int mProfileId;
-
private final boolean mPersistent;
private final int mMaxConns;
private final int mWaitTime;
private final int mMaxConnsTime;
-
private final int mMvnoType;
private final String mMvnoMatchData;
-
private final int mApnSetId;
-
private boolean mPermanentFailed = false;
private final int mCarrierId;
-
private final int mSkip464Xlat;
+ private final boolean mAlwaysOn;
/**
* Returns the MTU size of the IPv4 mobile interface to which the APN connected. Note this value
@@ -843,20 +835,37 @@
}
/**
- * Returns a bitmask describing the Radio Technologies(Network Types) which this APN may use.
+ * Returns a bitmask describing the Radio Technologies (Network Types) which this APN may use.
*
* NetworkType bitmask is calculated from NETWORK_TYPE defined in {@link TelephonyManager}.
*
* Examples of Network Types include {@link TelephonyManager#NETWORK_TYPE_UNKNOWN},
* {@link TelephonyManager#NETWORK_TYPE_GPRS}, {@link TelephonyManager#NETWORK_TYPE_EDGE}.
*
- * @return a bitmask describing the Radio Technologies(Network Types)
+ * @return a bitmask describing the Radio Technologies (Network Types) or 0 if it is undefined.
*/
public int getNetworkTypeBitmask() {
return mNetworkTypeBitmask;
}
/**
+ * Returns a bitmask describing the Radio Technologies (Network Types) that should not be torn
+ * down if it exists or brought up if it already exists for this APN.
+ *
+ * NetworkType bitmask is calculated from NETWORK_TYPE defined in {@link TelephonyManager}.
+ *
+ * Examples of Network Types include {@link TelephonyManager#NETWORK_TYPE_UNKNOWN},
+ * {@link TelephonyManager#NETWORK_TYPE_GPRS}, {@link TelephonyManager#NETWORK_TYPE_EDGE}.
+ *
+ * @return a bitmask describing the Radio Technologies (Network Types) that should linger
+ * or 0 if it is undefined.
+ * @hide
+ */
+ public @TelephonyManager.NetworkTypeBitMask long getLingeringNetworkTypeBitmask() {
+ return mLingeringNetworkTypeBitmask;
+ }
+
+ /**
* Returns the MVNO match type for this APN.
*
* @see Builder#setMvnoType(int)
@@ -888,6 +897,18 @@
return mSkip464Xlat;
}
+ /**
+ * Returns whether User Plane resources have to be activated during every transition from
+ * CM-IDLE mode to CM-CONNECTED state for this APN
+ * See 3GPP TS 23.501 section 5.6.13
+ *
+ * @return True if the PDU session for this APN should always be on and false otherwise
+ * @hide
+ */
+ public boolean isAlwaysOn() {
+ return mAlwaysOn;
+ }
+
private ApnSetting(Builder builder) {
this.mEntryName = builder.mEntryName;
this.mApnName = builder.mApnName;
@@ -912,6 +933,7 @@
this.mMtuV6 = builder.mMtuV6;
this.mCarrierEnabled = builder.mCarrierEnabled;
this.mNetworkTypeBitmask = builder.mNetworkTypeBitmask;
+ this.mLingeringNetworkTypeBitmask = builder.mLingeringNetworkTypeBitmask;
this.mProfileId = builder.mProfileId;
this.mPersistent = builder.mModemCognitive;
this.mMaxConns = builder.mMaxConns;
@@ -922,6 +944,7 @@
this.mApnSetId = builder.mApnSetId;
this.mCarrierId = builder.mCarrierId;
this.mSkip464Xlat = builder.mSkip464Xlat;
+ this.mAlwaysOn = builder.mAlwaysOn;
}
/**
@@ -938,6 +961,10 @@
networkTypeBitmask =
ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
}
+ int mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V4));
+ if (mtuV4 == -1) {
+ mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU));
+ }
return new Builder()
.setId(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)))
@@ -972,6 +999,8 @@
.setCarrierEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
Telephony.Carriers.CARRIER_ENABLED)) == 1)
.setNetworkTypeBitmask(networkTypeBitmask)
+ .setLingeringNetworkTypeBitmask(cursor.getInt(cursor.getColumnIndexOrThrow(
+ Carriers.LINGERING_NETWORK_TYPE_BITMASK)))
.setProfileId(cursor.getInt(
cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)))
.setModemCognitive(cursor.getInt(cursor.getColumnIndexOrThrow(
@@ -982,8 +1011,8 @@
cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME_RETRY)))
.setMaxConnsTime(cursor.getInt(cursor.getColumnIndexOrThrow(
Telephony.Carriers.TIME_LIMIT_FOR_MAX_CONNECTIONS)))
- .setMtuV4(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)))
- .setMtuV6(UNSET_MTU) // TODO: Add corresponding support in telephony provider
+ .setMtuV4(mtuV4)
+ .setMtuV6(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V6)))
.setMvnoType(getMvnoTypeIntFromString(
cursor.getString(cursor.getColumnIndexOrThrow(
Telephony.Carriers.MVNO_TYPE))))
@@ -994,6 +1023,7 @@
.setCarrierId(cursor.getInt(
cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)))
.setSkip464Xlat(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)))
+ .setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1)
.buildWithoutCheck();
}
@@ -1019,6 +1049,7 @@
.setRoamingProtocol(apn.mRoamingProtocol)
.setCarrierEnabled(apn.mCarrierEnabled)
.setNetworkTypeBitmask(apn.mNetworkTypeBitmask)
+ .setLingeringNetworkTypeBitmask(apn.mLingeringNetworkTypeBitmask)
.setProfileId(apn.mProfileId)
.setModemCognitive(apn.mPersistent)
.setMaxConns(apn.mMaxConns)
@@ -1031,6 +1062,7 @@
.setApnSetId(apn.mApnSetId)
.setCarrierId(apn.mCarrierId)
.setSkip464Xlat(apn.mSkip464Xlat)
+ .setAlwaysOn(apn.mAlwaysOn)
.buildWithoutCheck();
}
@@ -1069,9 +1101,11 @@
sb.append(", ").append(mMvnoMatchData);
sb.append(", ").append(mPermanentFailed);
sb.append(", ").append(mNetworkTypeBitmask);
+ sb.append(", ").append(mLingeringNetworkTypeBitmask);
sb.append(", ").append(mApnSetId);
sb.append(", ").append(mCarrierId);
sb.append(", ").append(mSkip464Xlat);
+ sb.append(", ").append(mAlwaysOn);
return sb.toString();
}
@@ -1136,8 +1170,9 @@
return Objects.hash(mApnName, mProxyAddress, mProxyPort, mMmsc, mMmsProxyAddress,
mMmsProxyPort, mUser, mPassword, mAuthType, mApnTypeBitmask, mId, mOperatorNumeric,
mProtocol, mRoamingProtocol, mMtuV4, mMtuV6, mCarrierEnabled, mNetworkTypeBitmask,
- mProfileId, mPersistent, mMaxConns, mWaitTime, mMaxConnsTime, mMvnoType,
- mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat);
+ mLingeringNetworkTypeBitmask, mProfileId, mPersistent, mMaxConns, mWaitTime,
+ mMaxConnsTime, mMvnoType, mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat,
+ mAlwaysOn);
}
@Override
@@ -1174,9 +1209,11 @@
&& Objects.equals(mMvnoType, other.mMvnoType)
&& Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
&& Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
+ && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
&& Objects.equals(mApnSetId, other.mApnSetId)
&& Objects.equals(mCarrierId, other.mCarrierId)
- && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
+ && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
+ && Objects.equals(mAlwaysOn, other.mAlwaysOn);
}
/**
@@ -1210,6 +1247,7 @@
&& Objects.equals(mPassword, other.mPassword)
&& Objects.equals(mAuthType, other.mAuthType)
&& Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
+ && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
&& (isDataRoaming || Objects.equals(mProtocol, other.mProtocol))
&& (!isDataRoaming || Objects.equals(mRoamingProtocol, other.mRoamingProtocol))
&& Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
@@ -1224,7 +1262,8 @@
&& Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
&& Objects.equals(mApnSetId, other.mApnSetId)
&& Objects.equals(mCarrierId, other.mCarrierId)
- && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
+ && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
+ && Objects.equals(mAlwaysOn, other.mAlwaysOn);
}
/**
@@ -1304,9 +1343,13 @@
apnValue.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled);
apnValue.put(Telephony.Carriers.MVNO_TYPE, getMvnoTypeStringFromInt(mMvnoType));
apnValue.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, mNetworkTypeBitmask);
+ apnValue.put(Telephony.Carriers.LINGERING_NETWORK_TYPE_BITMASK,
+ mLingeringNetworkTypeBitmask);
+ apnValue.put(Telephony.Carriers.MTU_V4, mMtuV4);
+ apnValue.put(Telephony.Carriers.MTU_V6, mMtuV6);
apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
-
+ apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn);
return apnValue;
}
@@ -1510,6 +1553,31 @@
return ServiceState.bitmaskHasTech(mNetworkTypeBitmask, networkType);
}
+ /**
+ * Check if this APN setting can support the given lingering network
+ *
+ * @param networkType The lingering network type
+ * @return {@code true} if this APN setting can support the given lingering network.
+ *
+ * @hide
+ */
+ public boolean canSupportLingeringNetworkType(@NetworkType int networkType) {
+ if (networkType == 0) {
+ return canSupportNetworkType(networkType);
+ }
+ // Do a special checking for GSM. In reality, GSM is a voice only network type and can never
+ // be used for data. We allow it here because in some DSDS corner cases, on the non-DDS
+ // sub, modem reports data rat unknown. In that case if voice is GSM and this APN supports
+ // GPRS or EDGE, this APN setting should be selected.
+ if (networkType == TelephonyManager.NETWORK_TYPE_GSM
+ && (mLingeringNetworkTypeBitmask & (TelephonyManager.NETWORK_TYPE_BITMASK_GPRS
+ | TelephonyManager.NETWORK_TYPE_BITMASK_EDGE)) != 0) {
+ return true;
+ }
+
+ return ServiceState.bitmaskHasTech((int) mLingeringNetworkTypeBitmask, networkType);
+ }
+
// Implement Parcelable.
@Override
/** @hide */
@@ -1537,6 +1605,7 @@
dest.writeInt(mRoamingProtocol);
dest.writeBoolean(mCarrierEnabled);
dest.writeInt(mNetworkTypeBitmask);
+ dest.writeLong(mLingeringNetworkTypeBitmask);
dest.writeInt(mProfileId);
dest.writeBoolean(mPersistent);
dest.writeInt(mMaxConns);
@@ -1549,6 +1618,7 @@
dest.writeInt(mApnSetId);
dest.writeInt(mCarrierId);
dest.writeInt(mSkip464Xlat);
+ dest.writeBoolean(mAlwaysOn);
}
private static ApnSetting readFromParcel(Parcel in) {
@@ -1570,6 +1640,7 @@
.setRoamingProtocol(in.readInt())
.setCarrierEnabled(in.readBoolean())
.setNetworkTypeBitmask(in.readInt())
+ .setLingeringNetworkTypeBitmask(in.readLong())
.setProfileId(in.readInt())
.setModemCognitive(in.readBoolean())
.setMaxConns(in.readInt())
@@ -1582,6 +1653,7 @@
.setApnSetId(in.readInt())
.setCarrierId(in.readInt())
.setSkip464Xlat(in.readInt())
+ .setAlwaysOn(in.readBoolean())
.buildWithoutCheck();
}
@@ -1649,7 +1721,8 @@
private int mRoamingProtocol = UNSPECIFIED_INT;
private int mMtuV4;
private int mMtuV6;
- private int mNetworkTypeBitmask;
+ private @TelephonyManager.NetworkTypeBitMask int mNetworkTypeBitmask;
+ private @TelephonyManager.NetworkTypeBitMask long mLingeringNetworkTypeBitmask;
private boolean mCarrierEnabled;
private int mProfileId;
private boolean mModemCognitive;
@@ -1661,6 +1734,7 @@
private int mApnSetId;
private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
+ private boolean mAlwaysOn;
/**
* Default constructor for Builder.
@@ -2012,6 +2086,19 @@
}
/**
+ * Sets lingering Radio Technology (Network Type) for this APN.
+ *
+ * @param lingeringNetworkTypeBitmask the Radio Technology (Network Type) that should linger
+ * @hide
+ */
+ @NonNull
+ public Builder setLingeringNetworkTypeBitmask(@TelephonyManager.NetworkTypeBitMask
+ long lingeringNetworkTypeBitmask) {
+ this.mLingeringNetworkTypeBitmask = lingeringNetworkTypeBitmask;
+ return this;
+ }
+
+ /**
* Sets the MVNO match type for this APN.
*
* @param mvnoType the MVNO match type to set for this APN
@@ -2048,6 +2135,18 @@
}
/**
+ * Sets whether the PDU session brought up by this APN should always be on.
+ * See 3GPP TS 23.501 section 5.6.13
+ *
+ * @param alwaysOn the always on status to set for this APN
+ * @hide
+ */
+ public Builder setAlwaysOn(boolean alwaysOn) {
+ this.mAlwaysOn = alwaysOn;
+ return this;
+ }
+
+ /**
* Builds {@link ApnSetting} from this builder.
*
* @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 2f03475..892eb29 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -402,6 +402,21 @@
}
/**
+ * Notify the system that a given DataProfile was unthrottled.
+ *
+ * @param dataProfile DataProfile associated with an APN returned from the modem
+ */
+ public final void notifyDataProfileUnthrottled(@NonNull DataProfile dataProfile) {
+ synchronized (mApnUnthrottledCallbacks) {
+ for (IDataServiceCallback callback : mApnUnthrottledCallbacks) {
+ mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED,
+ mSlotIndex, 0, new ApnUnthrottledIndication(dataProfile,
+ callback)).sendToTarget();
+ }
+ }
+ }
+
+ /**
* Called when the instance of data service is destroyed (e.g. got unbind or binder died)
* or when the data service provider is removed. The extended class should implement this
* method to perform cleanup works.
@@ -496,13 +511,20 @@
}
private static final class ApnUnthrottledIndication {
+ public final DataProfile dataProfile;
public final String apn;
public final IDataServiceCallback callback;
ApnUnthrottledIndication(String apn,
IDataServiceCallback callback) {
+ this.dataProfile = null;
this.apn = apn;
this.callback = callback;
}
+ ApnUnthrottledIndication(DataProfile dataProfile, IDataServiceCallback callback) {
+ this.dataProfile = dataProfile;
+ this.apn = null;
+ this.callback = callback;
+ }
}
private class DataServiceHandler extends Handler {
@@ -636,8 +658,13 @@
ApnUnthrottledIndication apnUnthrottledIndication =
(ApnUnthrottledIndication) message.obj;
try {
- apnUnthrottledIndication.callback
- .onApnUnthrottled(apnUnthrottledIndication.apn);
+ if (apnUnthrottledIndication.dataProfile != null) {
+ apnUnthrottledIndication.callback
+ .onDataProfileUnthrottled(apnUnthrottledIndication.dataProfile);
+ } else {
+ apnUnthrottledIndication.callback
+ .onApnUnthrottled(apnUnthrottledIndication.apn);
+ }
} catch (RemoteException e) {
loge("Failed to call onApnUnthrottled. " + e);
}
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index d082715..051d6c5 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -260,8 +260,8 @@
/**
* Unthrottles the APN on the current transport. There is no matching "APN throttle" method.
- * Instead, the APN is throttled for the time specified in
- * {@link DataCallResponse#getRetryDurationMillis}.
+ * Instead, the APN is throttled when {@link IDataService#setupDataCall} fails within
+ * the time specified by {@link DataCallResponse#getRetryDurationMillis}.
* <p/>
* see: {@link DataCallResponse#getRetryDurationMillis}
*
@@ -279,4 +279,27 @@
Rlog.e(TAG, "onApnUnthrottled: callback is null!");
}
}
+
+ /**
+ * Unthrottles the DataProfile on the current transport.
+ * There is no matching "DataProfile throttle" method.
+ * Instead, the DataProfile is throttled when {@link IDataService#setupDataCall} fails within
+ * the time specified by {@link DataCallResponse#getRetryDurationMillis}.
+ * <p/>
+ * see: {@link DataCallResponse#getRetryDurationMillis}
+ *
+ * @param dataProfile DataProfile containing the APN to be throttled
+ */
+ public void onDataProfileUnthrottled(final @NonNull DataProfile dataProfile) {
+ if (mCallback != null) {
+ try {
+ if (DBG) Rlog.d(TAG, "onDataProfileUnthrottled");
+ mCallback.onDataProfileUnthrottled(dataProfile);
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "onDataProfileUnthrottled: remote exception", e);
+ }
+ } else {
+ Rlog.e(TAG, "onDataProfileUnthrottled: callback is null!");
+ }
+ }
}
diff --git a/telephony/java/android/telephony/data/IDataServiceCallback.aidl b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
index 9cc2fea..8205b5e 100644
--- a/telephony/java/android/telephony/data/IDataServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
@@ -17,6 +17,7 @@
package android.telephony.data;
import android.telephony.data.DataCallResponse;
+import android.telephony.data.DataProfile;
/**
* The call back interface
@@ -33,4 +34,5 @@
void onHandoverStarted(int result);
void onHandoverCancelled(int result);
void onApnUnthrottled(in String apn);
+ void onDataProfileUnthrottled(in DataProfile dp);
}
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index e1aec0a..ab35d77 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -17,6 +17,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
@@ -24,6 +25,7 @@
import android.os.RemoteException;
import android.service.euicc.EuiccProfileInfo;
import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.telephony.euicc.IAuthenticateServerCallback;
@@ -122,7 +124,6 @@
/** Result code indicating the caller is not the active LPA. */
public static final int RESULT_CALLER_NOT_ALLOWED = -3;
-
/**
* Callback to receive the result of an eUICC card API.
*
@@ -220,12 +221,48 @@
* @param refresh Whether sending the REFRESH command to modem.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @deprecated instead use {@link #disableProfile(String, String, int, boolean, Executor,
+ * ResultCallback)}
*/
+ @Deprecated
public void disableProfile(String cardId, String iccid, boolean refresh,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
try {
getIEuiccCardController().disableProfile(mContext.getOpPackageName(), cardId, iccid,
- refresh, new IDisableProfileCallback.Stub() {
+ TelephonyManager.DEFAULT_PORT_INDEX, refresh,
+ new IDisableProfileCallback.Stub() {
+ @Override
+ public void onComplete(int resultCode) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.onComplete(resultCode, null));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling disableProfile", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ /**
+ * Disables the profile of the given ICCID.
+ *
+ * @param cardId The Id of the eUICC.
+ * @param iccid The iccid of the profile.
+ * @param portIndex the Port index is the unique index referring to a port.
+ * @param refresh Whether sending the REFRESH command to modem.
+ * @param executor The executor through which the callback should be invoked.
+ * @param callback The callback to get the result code.
+ */
+ public void disableProfile(@Nullable String cardId, @Nullable String iccid, int portIndex,
+ boolean refresh, @NonNull @CallbackExecutor Executor executor,
+ @NonNull ResultCallback<Void> callback) {
+ try {
+ getIEuiccCardController().disableProfile(mContext.getOpPackageName(), cardId, iccid,
+ portIndex, refresh, new IDisableProfileCallback.Stub() {
@Override
public void onComplete(int resultCode) {
final long token = Binder.clearCallingIdentity();
@@ -251,12 +288,51 @@
* @param refresh Whether sending the REFRESH command to modem.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the EuiccProfileInfo enabled.
+ *
+ * @deprecated instead use {@link #switchToProfile(String, String, int, boolean, Executor,
+ * ResultCallback)}
*/
+ @Deprecated
public void switchToProfile(String cardId, String iccid, boolean refresh,
@CallbackExecutor Executor executor, ResultCallback<EuiccProfileInfo> callback) {
try {
getIEuiccCardController().switchToProfile(mContext.getOpPackageName(), cardId, iccid,
- refresh, new ISwitchToProfileCallback.Stub() {
+ TelephonyManager.DEFAULT_PORT_INDEX, refresh,
+ new ISwitchToProfileCallback.Stub() {
+ @Override
+ public void onComplete(int resultCode, EuiccProfileInfo profile) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.onComplete(resultCode, profile));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling switchToProfile", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Switches from the current profile to another profile. The current profile will be disabled
+ * and the specified profile will be enabled. Here portIndex specifies on which port the
+ * profile is to be enabled.
+ *
+ * @param cardId The Id of the eUICC.
+ * @param iccid The iccid of the profile to switch to.
+ * @param portIndex The Port index is the unique index referring to a port.
+ * @param refresh Whether sending the REFRESH command to modem.
+ * @param executor The executor through which the callback should be invoked.
+ * @param callback The callback to get the result code and the EuiccProfileInfo enabled.
+ */
+ public void switchToProfile(@Nullable String cardId, @Nullable String iccid, int portIndex,
+ boolean refresh, @NonNull @CallbackExecutor Executor executor,
+ @NonNull ResultCallback<EuiccProfileInfo> callback) {
+ try {
+ getIEuiccCardController().switchToProfile(mContext.getOpPackageName(), cardId, iccid,
+ portIndex, refresh, new ISwitchToProfileCallback.Stub() {
@Override
public void onComplete(int resultCode, EuiccProfileInfo profile) {
final long token = Binder.clearCallingIdentity();
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 2edb564c..45022a6 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -1418,4 +1418,22 @@
.getEuiccControllerService()
.get());
}
+
+ /**
+ * Returns whether the passing portIndex is available.
+ * A port is available if it has no profiles enabled on it or calling app has carrier privilege
+ * over the profile installed on the selected port.
+ * Always returns false if the cardId is a physical card.
+ *
+ * @param portIndex is an enumeration of the ports available on the UICC.
+ * @return {@code true} if port is available
+ */
+ public boolean isSimPortAvailable(int portIndex) {
+ try {
+ return getIEuiccController().isSimPortAvailable(mCardId, portIndex,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
index 793c377..c25ace0 100644
--- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java
+++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
@@ -50,6 +50,7 @@
private String mRcsProfile;
private String mClientVendor;
private String mClientVersion;
+ private boolean mRcsEnabledByUser;
/**
* Create a RcsClientConfiguration object.
@@ -63,14 +64,41 @@
* @param clientVersion Identifies the RCS client version. Refer to GSMA
* RCC.07 "client_version" parameter.
* Example:client_version=RCSAndrd-1.0
+ * @deprecated Use {@link #RcsClientConfiguration(String, String, String, String, boolean)}
+ * instead. Deprecated prototype assumes that the user setting controlling RCS is enabled.
*/
+ @Deprecated
public RcsClientConfiguration(@NonNull String rcsVersion,
@NonNull @StringRcsProfile String rcsProfile,
@NonNull String clientVendor, @NonNull String clientVersion) {
+ this(rcsVersion, rcsProfile, clientVendor, clientVersion, true);
+ }
+
+ /**
+ * Create a RcsClientConfiguration object.
+ * Default messaging application must pass a valid configuration object
+ * @param rcsVersion The parameter identifies the RCS version supported
+ * by the client. Refer to GSMA RCC.07 "rcs_version" parameter.
+ * @param rcsProfile Identifies a fixed set of RCS services that are
+ * supported by the client. See {@link #RCS_PROFILE_1_0 } or
+ * {@link #RCS_PROFILE_2_3 }
+ * @param clientVendor Identifies the vendor providing the RCS client.
+ * @param clientVersion Identifies the RCS client version. Refer to GSMA
+ * RCC.07 "client_version" parameter.
+ * Example:client_version=RCSAndrd-1.0
+ * @param isRcsEnabledByUser The current user setting for whether or not the user has
+ * enabled or disabled RCS. Please refer to GSMA RCC.07 "rcs_state" parameter for how this
+ * can affect provisioning.
+ */
+ public RcsClientConfiguration(@NonNull String rcsVersion,
+ @NonNull @StringRcsProfile String rcsProfile,
+ @NonNull String clientVendor, @NonNull String clientVersion,
+ boolean isRcsEnabledByUser) {
mRcsVersion = rcsVersion;
mRcsProfile = rcsProfile;
mClientVendor = clientVendor;
mClientVersion = clientVersion;
+ mRcsEnabledByUser = isRcsEnabledByUser;
}
/**
@@ -102,6 +130,18 @@
}
/**
+ * The current user setting provided by the RCS messaging application that determines
+ * whether or not the user has enabled RCS.
+ * <p>
+ * See GSMA RCC.07 "rcs_state" parameter for more information about how this setting
+ * affects provisioning.
+ * @return true if RCS is enabled by the user, false if RCS is disabled by the user.
+ */
+ public boolean isRcsEnabledByUser() {
+ return mRcsEnabledByUser;
+ }
+
+ /**
* {@link Parcelable#writeToParcel}
*/
@Override
@@ -110,6 +150,7 @@
out.writeString(mRcsProfile);
out.writeString(mClientVendor);
out.writeString(mClientVersion);
+ out.writeBoolean(mRcsEnabledByUser);
}
/**
@@ -124,8 +165,9 @@
String rcsProfile = in.readString();
String clientVendor = in.readString();
String clientVersion = in.readString();
+ Boolean rcsEnabledByUser = in.readBoolean();
return new RcsClientConfiguration(rcsVersion, rcsProfile,
- clientVendor, clientVersion);
+ clientVendor, clientVersion, rcsEnabledByUser);
}
@Override
@@ -152,11 +194,13 @@
return mRcsVersion.equals(other.mRcsVersion) && mRcsProfile.equals(other.mRcsProfile)
&& mClientVendor.equals(other.mClientVendor)
- && mClientVersion.equals(other.mClientVersion);
+ && mClientVersion.equals(other.mClientVersion)
+ && (mRcsEnabledByUser == other.mRcsEnabledByUser);
}
@Override
public int hashCode() {
- return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion);
+ return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion,
+ mRcsEnabledByUser);
}
}
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 7a1c092..61de3ac 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -28,13 +28,10 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
-import android.telephony.ims.feature.RcsFeature;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -337,6 +334,14 @@
@SystemApi
public static final int PUBLISH_STATE_OTHER_ERROR = 6;
+ /**
+ * The device is currently trying to publish its capabilities to the network.
+ * @hide
+ */
+ @SystemApi
+ public static final int PUBLISH_STATE_PUBLISHING = 7;
+
+
/**@hide*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "PUBLISH_STATE_", value = {
@@ -345,7 +350,8 @@
PUBLISH_STATE_VOICE_PROVISION_ERROR,
PUBLISH_STATE_RCS_PROVISION_ERROR,
PUBLISH_STATE_REQUEST_TIMEOUT,
- PUBLISH_STATE_OTHER_ERROR
+ PUBLISH_STATE_OTHER_ERROR,
+ PUBLISH_STATE_PUBLISHING
})
public @interface PublishState {}
@@ -480,9 +486,12 @@
* <p>
* Be sure to check the availability of this feature using
* {@link ImsRcsManager#isAvailable(int, int)} and ensuring
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
- * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
+ * {@link
+ * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
+ * {@link
+ * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
+ * enabled or else this operation will fail with {@link #ERROR_NOT_AVAILABLE} or
+ * {@link #ERROR_NOT_ENABLED}.
*
* @param contactNumbers A list of numbers that the capabilities are being requested for.
* @param executor The executor that will be used when the request is completed and the
@@ -573,8 +582,10 @@
* <p>
* Be sure to check the availability of this feature using
* {@link ImsRcsManager#isAvailable(int, int)} and ensuring
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
+ * {@link
+ * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
+ * {@link
+ * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
* enabled or else this operation will fail with
* {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
*
@@ -690,7 +701,8 @@
* Registers a {@link OnPublishStateChangedListener} with the system, which will provide publish
* state updates for the subscription specified in {@link ImsManager@getRcsManager(subid)}.
* <p>
- * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to subscription
+ * Use {@link android.telephony.SubscriptionManager.OnSubscriptionsChangedListener} to listen
+ * to subscription
* changed events and call
* {@link #removeOnPublishStateChangedListener(OnPublishStateChangedListener)} to clean up.
* <p>
@@ -792,7 +804,8 @@
* cache associated with those contacts as the local cache becomes stale.
* <p>
* This setting will only enable this feature if
- * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is also enabled.
+ * {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is
+ * also enabled.
* <p>
* Note: This setting does not affect whether or not the device publishes its service
* capabilities if the subscription supports presence publication.
@@ -843,7 +856,8 @@
* session.
* <p>
* This setting will only enable this feature if
- * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is also enabled.
+ * {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is
+ * also enabled.
* <p>
* Note: This setting does not affect whether or not the device publishes its service
* capabilities if the subscription supports presence publication.
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index c3d7325..c27fa4f 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -81,6 +81,26 @@
}
/**
+ * Receives the status of changes in the publishing connection from ims service
+ * and deliver this callback to the framework.
+ */
+ public void onPublishUpdated(int reasonCode, @NonNull String reasonPhrase,
+ int reasonHeaderCause, @NonNull String reasonHeaderText) throws ImsException {
+ ICapabilityExchangeEventListener listener = mListenerBinder;
+ if (listener == null) {
+ return;
+ }
+ try {
+ listener.onPublishUpdated(reasonCode, reasonPhrase,
+ reasonHeaderCause, reasonHeaderText);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "onPublishUpdated exception: " + e);
+ throw new ImsException("Remote is not available",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ }
+
+ /**
* Receives the callback of the remote capability request from the network and deliver this
* request to the framework.
*/
diff --git a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
index 078ac91..c675bc3 100644
--- a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
@@ -31,6 +31,8 @@
oneway interface ICapabilityExchangeEventListener {
void onRequestPublishCapabilities(int publishTriggerType);
void onUnpublish();
+ void onPublishUpdated(int reasonCode, String reasonPhrase, int reasonHeaderCause,
+ String reasonHeaderText);
void onRemoteCapabilityRequest(in Uri contactUri,
in List<String> remoteCapabilities, IOptionsRequestCallback cb);
}
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index a3be8da..7a1a2af 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -90,6 +90,30 @@
void onUnpublish() throws ImsException;
/**
+ * Notify the framework that the ImsService has refreshed the PUBLISH
+ * internally, which has resulted in a new PUBLISH result.
+ * <p>
+ * This method must be called to notify the framework of SUCCESS (200 OK) and FAILURE (300+)
+ * codes in order to keep the AOSP stack up to date.
+ * @param reasonCode The SIP response code sent from the network.
+ * @param reasonPhrase The optional reason response from the network. If the
+ * network provided no reason with the sip code, the string should be empty.
+ * @param reasonHeaderCause The “cause” parameter of the “reason” header
+ * included in the SIP message.
+ * @param reasonHeaderText The “text” parameter of the “reason” header
+ * included in the SIP message.
+ * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
+ * currently connected to the framework. This can happen if the {@link RcsFeature} is not
+ * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received
+ * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare
+ * cases when the Telephony stack has crashed.
+ *
+ */
+ default void onPublishUpdated(int reasonCode, @NonNull String reasonPhrase,
+ int reasonHeaderCause, @NonNull String reasonHeaderText) throws ImsException {
+ }
+
+ /**
* Inform the framework of an OPTIONS query from a remote device for this device's UCE
* capabilities.
* <p>
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 2df5f53..167aa07 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -77,6 +77,7 @@
import android.telephony.UiccCardInfo;
import android.telephony.UiccSlotInfo;
+import android.telephony.UiccSlotMapping;
/**
* Interface used to interact with the phone. Mostly this is used by the
@@ -900,6 +901,8 @@
* Perform a radio network scan and return the id of this scan.
*
* @param subId the id of the subscription.
+ * @param renounceFineLocationAccess Set this to true if the caller would not like to
+ * receive fine location related information
* @param request Defines all the configs for network scan.
* @param messenger Callback messages will be sent using this messenger.
* @param binder the binder object instantiated in TelephonyManager.
@@ -907,8 +910,9 @@
* @param callingFeatureId The feature in the package
* @return An id for this scan.
*/
- int requestNetworkScan(int subId, in NetworkScanRequest request, in Messenger messenger,
- in IBinder binder, in String callingPackage, String callingFeatureId);
+ int requestNetworkScan(int subId, in boolean renounceFineLocationAccess,
+ in NetworkScanRequest request, in Messenger messenger, in IBinder binder,
+ in String callingPackage, String callingFeatureId);
/**
* Stop an existing radio network scan.
@@ -1387,12 +1391,17 @@
/**
* Get the service state on specified subscription
* @param subId Subscription id
+ * @param renounceFineLocationAccess Set this to true if the caller would not like to
+ * receive fine location related information
+ * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
+ * receive coarse location related information
* @param callingPackage The package making the call
* @param callingFeatureId The feature in the package
* @return Service state on specified subscription.
*/
- ServiceState getServiceStateForSubscriber(int subId, String callingPackage,
- String callingFeatureId);
+ ServiceState getServiceStateForSubscriber(int subId, boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess,
+ String callingPackage, String callingFeatureId);
/**
* Returns the URI for the per-account voicemail ringtone set in Phone settings.
@@ -1734,17 +1743,35 @@
* @return UiccSlotInfo array.
* @hide
*/
- UiccSlotInfo[] getUiccSlotsInfo();
+ UiccSlotInfo[] getUiccSlotsInfo(String callingPackage);
/**
* Map logicalSlot to physicalSlot, and activate the physicalSlot if it is inactive.
* @param physicalSlots Index i in the array representing physical slot for phone i. The array
* size should be same as getPhoneCount().
+ * @deprecated Use {@link #setSimSlotMapping(in List<UiccSlotMapping> slotMapping)} instead.
* @return boolean Return true if the switch succeeds, false if the switch fails.
*/
boolean switchSlots(in int[] physicalSlots);
/**
+ * Maps the logical slots to the SlotPortMapping which consist of both physical slot index and
+ * port index. Logical slot is the slot that is seen by modem. Physical slot is the actual
+ * physical slot. Port index is the index (enumerated value) for the associated port available
+ * on the SIM. Each physical slot can have multiple ports which enables multi-enabled profile
+ * (MEP). If eUICC physical slot supports 2 ports, then the port index is numbered 0,1 and if
+ * eUICC2 supports 4 ports then the port index is numbered 0,1,2,3. Each portId is unique within
+ * a UICC physical slot but not necessarily unique across UICC’s. SEP(Single enabled profile)
+ * eUICC and non-eUICC will only have port Index 0.
+ *
+ * Logical slots that are already mapped to the requested SlotPortMapping are not impacted.
+ * @param slotMapping Index i in the list representing slot mapping for phone i.
+ *
+ * @return {@code true} if the switch succeeds, {@code false} if the switch fails.
+ */
+ boolean setSimSlotMapping(in List<UiccSlotMapping> slotMapping);
+
+ /**
* Returns whether mobile data roaming is enabled on the subscription with id {@code subId}.
*
* @param subId the subscription id
@@ -2122,7 +2149,7 @@
/**
* Get the mapping from logical slots to physical slots.
*/
- int[] getSlotsMapping();
+ int[] getSlotsMapping(String callingPackage);
/**
* Get the IRadio HAL Version encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown
@@ -2518,4 +2545,14 @@
*/
CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
String callingFeatureId);
+
+ /**
+ * @return true if the modem service is set successfully, false otherwise.
+ */
+ boolean setModemService(in String serviceName);
+
+ /**
+ * @return the service name of the modem service which bind to.
+ */
+ String getModemService();
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 3a99f0e0..f650246 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -171,6 +171,8 @@
public static final String SLOT_KEY = "slot";
+ public static final String PORT_KEY = "port";
+
// FIXME: This is used to pass a subId via intents, we need to look at its usage, which is
// FIXME: extensive, and see if this should be an array of all active subId's or ...?
/**
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 866fd2c..ba95841 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -530,6 +530,8 @@
int RIL_REQUEST_GET_SLICING_CONFIG = 224;
int RIL_REQUEST_ENABLE_VONR = 225;
int RIL_REQUEST_IS_VONR_ENABLED = 226;
+ int RIL_REQUEST_SET_USAGE_SETTING = 227;
+ int RIL_REQUEST_GET_USAGE_SETTING = 228;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
index e33f44c..c717c09 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
@@ -45,10 +45,10 @@
in IGetAllProfilesCallback callback);
oneway void getProfile(String callingPackage, String cardId, String iccid,
in IGetProfileCallback callback);
- oneway void disableProfile(String callingPackage, String cardId, String iccid, boolean refresh,
- in IDisableProfileCallback callback);
- oneway void switchToProfile(String callingPackage, String cardId, String iccid, boolean refresh,
- in ISwitchToProfileCallback callback);
+ oneway void disableProfile(String callingPackage, String cardId, String iccid, int portIndex,
+ boolean refresh, in IDisableProfileCallback callback);
+ oneway void switchToProfile(String callingPackage, String cardId, String iccid, int portIndex,
+ boolean refresh, in ISwitchToProfileCallback callback);
oneway void setNickname(String callingPackage, String cardId, String iccid, String nickname,
in ISetNicknameCallback callback);
oneway void deleteProfile(String callingPackage, String cardId, String iccid,
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 35e8a12..944ce348 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -51,4 +51,5 @@
void setSupportedCountries(boolean isSupported, in List<String> countriesList);
List<String> getSupportedCountries(boolean isSupported);
boolean isSupportedCountry(String countryIso);
+ boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage);
}
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 5b44dba..21c3f76 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -23,6 +23,7 @@
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
+import android.telephony.UiccPortInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.GsmAlphabet;
@@ -44,8 +45,7 @@
static final int FPLMN_BYTE_SIZE = 3;
// ICCID used for tests by some OEMs
- // TODO(b/159354974): Replace the constant here with UiccPortInfo.ICCID_REDACTED once ready
- private static final String TEST_ICCID = "FFFFFFFFFFFFFFFFFFFF";
+ public static final String TEST_ICCID = UiccPortInfo.ICCID_REDACTED;
// A table mapping from a number to a hex character for fast encoding hex strings.
private static final char[] HEX_CHARS = {
diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk
index ce5e4cf..284008c 100644
--- a/test-legacy/Android.mk
+++ b/test-legacy/Android.mk
@@ -41,6 +41,6 @@
include $(BUILD_STATIC_JAVA_LIBRARY)
# Archive a copy of the classes.jar in SDK build.
-$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar)
+$(call dist-for-goals,sdk,$(full_classes_jar):android.test.legacy.jar)
endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
diff --git a/tests/Codegen/OWNERS b/tests/Codegen/OWNERS
index da723b3..e69de29 100644
--- a/tests/Codegen/OWNERS
+++ b/tests/Codegen/OWNERS
@@ -1 +0,0 @@
-eugenesusla@google.com
\ No newline at end of file
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index f0ab63e..06200cd 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -449,8 +449,7 @@
// Query proper module name
result = getPackageManagerNative().getStagedApexInfo(TEST_APEX_PACKAGE_NAME);
assertThat(result.moduleName).isEqualTo(TEST_APEX_PACKAGE_NAME);
- assertThat(result.hasBootClassPathJars).isTrue();
- assertThat(result.hasSystemServerClassPathJars).isTrue();
+ assertThat(result.hasClassPathJars).isTrue();
InstallUtils.openPackageInstallerSession(sessionId).abandon();
}
diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS
index 4f655e5..a96d15e 100644
--- a/tools/aapt2/OWNERS
+++ b/tools/aapt2/OWNERS
@@ -1,4 +1,3 @@
set noparent
-toddke@google.com
zyy@google.com
patb@google.com
\ No newline at end of file
diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS
index da723b3..e69de29 100644
--- a/tools/codegen/OWNERS
+++ b/tools/codegen/OWNERS
@@ -1 +0,0 @@
-eugenesusla@google.com
\ No newline at end of file