Merge "Prepare to restrict PI sender BAL privileges."
diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
new file mode 100644
index 0000000..3d447ac
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 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.input
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.perftests.utils.PerfStatusReporter
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.PointerCoords
+import android.view.MotionEvent.PointerProperties
+import android.view.MotionPredictor
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.filters.LargeTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+import java.time.Duration
+
+private fun getStylusMotionEvent(
+ eventTime: Duration,
+ action: Int,
+ x: Float,
+ y: Float,
+ ): MotionEvent{
+ val pointerCount = 1
+ val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
+ val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
+
+ for (i in 0 until pointerCount) {
+ properties[i] = PointerProperties()
+ properties[i]!!.id = i
+ properties[i]!!.toolType = MotionEvent.TOOL_TYPE_STYLUS
+ coords[i] = PointerCoords()
+ coords[i]!!.x = x
+ coords[i]!!.y = y
+ }
+
+ return MotionEvent.obtain(/*downTime=*/0, eventTime.toMillis(), action, properties.size,
+ properties, coords, /*metaState=*/0, /*buttonState=*/0,
+ /*xPrecision=*/0f, /*yPrecision=*/0f, /*deviceId=*/0, /*edgeFlags=*/0,
+ InputDevice.SOURCE_STYLUS, /*flags=*/0)
+}
+
+private fun getPredictionContext(offset: Duration, enablePrediction: Boolean): Context {
+ val context = mock(Context::class.java)
+ val resources: Resources = mock(Resources::class.java)
+ `when`(context.getResources()).thenReturn(resources)
+ `when`(resources.getInteger(
+ com.android.internal.R.integer.config_motionPredictionOffsetNanos)).thenReturn(
+ offset.toNanos().toInt())
+ `when`(resources.getBoolean(
+ com.android.internal.R.bool.config_enableMotionPrediction)).thenReturn(enablePrediction)
+ return context
+}
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class MotionPredictorBenchmark {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ @get:Rule
+ val perfStatusReporter = PerfStatusReporter()
+ private val initialPropertyValue =
+ SystemProperties.get("persist.input.enable_motion_prediction")
+
+ private var eventTime = Duration.ofMillis(1)
+
+ @Before
+ fun setUp() {
+ instrumentation.uiAutomation.executeShellCommand(
+ "setprop persist.input.enable_motion_prediction true")
+ }
+
+ @After
+ fun tearDown() {
+ instrumentation.uiAutomation.executeShellCommand(
+ "setprop persist.input.enable_motion_prediction $initialPropertyValue")
+ }
+
+ /**
+ * In a typical usage, app will send the event to the predictor and then call .predict to draw
+ * a prediction. In a loop, we keep sending MOVE and then calling .predict to simulate this.
+ */
+ @Test
+ fun timeRecordAndPredict() {
+ val offset = Duration.ofMillis(1)
+ val predictor = MotionPredictor(getPredictionContext(offset, /*enablePrediction=*/true))
+ // ACTION_DOWN t=0 x=0 y=0
+ predictor.record(getStylusMotionEvent(eventTime, ACTION_DOWN, /*x=*/0f, /*y=*/0f))
+
+ val state = perfStatusReporter.getBenchmarkState()
+ while (state.keepRunning()) {
+ eventTime += Duration.ofMillis(1)
+
+ // Send MOVE event and then call .predict
+ val moveEvent = getStylusMotionEvent(eventTime, ACTION_MOVE, /*x=*/1f, /*y=*/2f)
+ predictor.record(moveEvent)
+ val predictionTime = eventTime + Duration.ofMillis(2)
+ val predicted = predictor.predict(predictionTime.toNanos())
+ assertEquals(1, predicted.size)
+ assertEquals((predictionTime + offset).toMillis(), predicted[0].eventTime)
+ }
+ }
+
+ /**
+ * The creation of the predictor should happen infrequently. However, we still want to be
+ * mindful of the load times.
+ */
+ @Test
+ fun timeCreatePredictor() {
+ val context = getPredictionContext(
+ /*offset=*/Duration.ofMillis(1), /*enablePrediction=*/true)
+
+ val state = perfStatusReporter.getBenchmarkState()
+ while (state.keepRunning()) {
+ MotionPredictor(context)
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/input/OWNERS b/apct-tests/perftests/core/src/android/input/OWNERS
new file mode 100644
index 0000000..95e3f02
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/input/OWNERS
@@ -0,0 +1,3 @@
+include platform/frameworks/base:/INPUT_OWNERS
+
+# Bug component: 136048
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 1cd5d96..a976de3 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -19,6 +19,8 @@
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.RenderNode;
+import android.graphics.text.LineBreakConfig;
+import android.os.LocaleList;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
@@ -43,6 +45,19 @@
public StaticLayoutPerfTest() {}
+ public static final String JP_TEXT_SHORT = "日本語でのパフォーマンス計測のための例文です。";
+ // About 350 chars
+ public static final String JP_TEXT_LONG = "日本語でのパフォーマンス計測のための文章ですが、長いです。"
+ + "長い文章が必要なのですが、特に書くことが思いつかないので、コロッケの作り方でも書こうと思います。"
+ + "じゃがいもを茹でて潰しておきます。私は少し形が残っているほうが好きなので、ある程度のところで潰すのを"
+ + "やめます。別のフライパンで軽く塩をして玉ねぎのみじん切りを炒め、透き通ったら、一度取り出します。"
+ + "きれいにしたフライパンに、豚ひき肉を入れてあまりイジらずに豚肉を炒めます。"
+ + "しっかり火が通ったら炒めた玉ねぎを戻し入れ、塩コショウで味を決めます。"
+ + "炒めた肉玉ねぎとじゃがいもをよく混ぜて、1個あたり100gになるように整形します。"
+ + "整形したタネに小麦粉、卵、パン粉をつけて揚げます。"
+ + "180℃で揚げ、衣がきつね色になったら引き上げて、油を切る。"
+ + "盛り付けて出来上がり。";
+
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -432,4 +447,117 @@
}
}
+ @Test
+ public void testCreate_JPText_Phrase_Short() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final String text = JP_TEXT_SHORT;
+ final LineBreakConfig config = new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ .build();
+ final TextPaint paint = new TextPaint(PAINT);
+ paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+ StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+ .setLineBreakConfig(config)
+ .build();
+ }
+ }
+
+ @Test
+ public void testCreate_JPText_Phrase_Long() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final String text = JP_TEXT_LONG;
+ final LineBreakConfig config = new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ .build();
+ final TextPaint paint = new TextPaint(PAINT);
+ paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+ StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+ .setLineBreakConfig(config)
+ .build();
+ }
+ }
+
+ @Test
+ public void testCreate_JPText_Phrase_LongLong() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final String text = JP_TEXT_LONG.repeat(20); // 250 * 20 = 7000 chars
+ final LineBreakConfig config = new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ .build();
+ final TextPaint paint = new TextPaint(PAINT);
+ paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+ StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+ .setLineBreakConfig(config)
+ .build();
+ }
+ }
+
+ @Test
+ public void testCreate_JPText_NoPhrase_Short() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final String text = JP_TEXT_SHORT;
+ final LineBreakConfig config = new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ final TextPaint paint = new TextPaint(PAINT);
+ paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+ StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+ .setLineBreakConfig(config)
+ .build();
+ }
+ }
+
+ @Test
+ public void testCreate_JPText_NoPhrase_Long() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final String text = JP_TEXT_LONG;
+ final LineBreakConfig config = new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ final TextPaint paint = new TextPaint(PAINT);
+ paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+ StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+ .setLineBreakConfig(config)
+ .build();
+ }
+ }
+
+ @Test
+ public void testCreate_JPText_NoPhrase_LongLong() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final String text = JP_TEXT_LONG.repeat(20); // 250 * 20 = 7000 chars
+ final LineBreakConfig config = new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ final TextPaint paint = new TextPaint(PAINT);
+ paint.setTextLocales(LocaleList.forLanguageTags("ja-JP"));
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+ StaticLayout.Builder.obtain(text, 0, text.length(), paint, TEXT_WIDTH)
+ .setLineBreakConfig(config)
+ .build();
+ }
+ }
}
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index 5befa1f..424d784 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -15,8 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.perftests.multiuser">
-
+ package="com.android.perftests.multiuser">
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
@@ -27,6 +26,7 @@
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER" />
<application>
<uses-library android:name="android.test.runner" />
@@ -38,5 +38,4 @@
<queries>
<package android:name="perftests.multiuser.apps.dummyapp" />
</queries>
-
</manifest>
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index b24076a..64ba6d1 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -15,7 +15,11 @@
*/
package android.multiuser;
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
@@ -25,6 +29,7 @@
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.WaitResult;
+import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -34,6 +39,7 @@
import android.content.pm.IPackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IProgressListener;
@@ -59,6 +65,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -114,6 +121,7 @@
private ActivityManager mAm;
private IActivityManager mIam;
private PackageManager mPm;
+ private WallpaperManager mWm;
private ArrayList<Integer> mUsersToRemove;
private boolean mHasManagedUserFeature;
private BroadcastWaiter mBroadcastWaiter;
@@ -132,6 +140,7 @@
mIam = ActivityManager.getService();
mUsersToRemove = new ArrayList<>();
mPm = context.getPackageManager();
+ mWm = WallpaperManager.getInstance(context);
mHasManagedUserFeature = mPm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
mBroadcastWaiter = new BroadcastWaiter(context, TAG, TIMEOUT_IN_SECOND,
Intent.ACTION_USER_STARTED,
@@ -241,24 +250,27 @@
/**
* Tests starting an uninitialized user, with wait times in between iterations.
- * Measures the time until ACTION_USER_STARTED is received.
+ * Measures the time until the ProgressListener callback.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void startUser_realistic() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
+ final ProgressWaiter waiter = new ProgressWaiter();
waitForBroadcastIdle();
- runThenWaitForBroadcasts(userId, () -> {
- mRunner.resumeTiming();
- Log.i(TAG, "Starting timer");
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
- mIam.startUserInBackground(userId);
- }, Intent.ACTION_USER_STARTED);
+ final boolean success = mIam.startUserInBackgroundWithListener(userId, waiter)
+ && waiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
+
+ assertTrue("Error: could not start user " + userId, success);
+
removeUser(userId);
waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
@@ -372,6 +384,32 @@
removeUser(testUser);
}
+ /** Tests switching to a previously-started, but no-longer-running, user with wait
+ * times between iterations and using a static wallpaper */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void switchUser_stopped_staticWallpaper() throws RemoteException {
+ assumeTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+ final int startUser = ActivityManager.getCurrentUser();
+ final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true,
+ /* useStaticWallpaper */true);
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ waitCoolDownPeriod();
+ Log.d(TAG, "Starting timer");
+ mRunner.resumeTiming();
+
+ switchUser(testUser);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ switchUserNoCheck(startUser);
+ stopUserAfterWaitingForBroadcastIdle(testUser, true);
+ attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
+ mRunner.resumeTimingForNextIteration();
+ }
+ removeUser(testUser);
+ }
+
/** Tests switching to an already-created already-running non-owner background user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void switchUser_running() throws RemoteException {
@@ -415,6 +453,31 @@
removeUser(testUser);
}
+ /** Tests switching to an already-created already-running non-owner background user, with wait
+ * times between iterations and using a default static wallpaper */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void switchUser_running_staticWallpaper() throws RemoteException {
+ assumeTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+ final int startUser = ActivityManager.getCurrentUser();
+ final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false,
+ /* useStaticWallpaper */ true);
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ waitCoolDownPeriod();
+ Log.d(TAG, "Starting timer");
+ mRunner.resumeTiming();
+
+ switchUser(testUser);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ waitForBroadcastIdle();
+ switchUserNoCheck(startUser);
+ mRunner.resumeTimingForNextIteration();
+ }
+ removeUser(testUser);
+ }
+
/** Tests stopping a background user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void stopUser() throws RemoteException {
@@ -843,14 +906,20 @@
waitForLatch("Failed to properly stop user " + userId, latch);
}
+ private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws RemoteException {
+ return initializeNewUserAndSwitchBack(stopNewUser, /* useStaticWallpaper */ false);
+ }
+
/**
* Creates a user and waits for its ACTION_USER_UNLOCKED.
* Then switches to back to the original user and waits for its switchUser() to finish.
*
* @param stopNewUser whether to stop the new user after switching to otherUser.
+ * @param useStaticWallpaper whether to switch the wallpaper of the default user to a static.
* @return userId of the newly created user.
*/
- private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws RemoteException {
+ private int initializeNewUserAndSwitchBack(boolean stopNewUser, boolean useStaticWallpaper)
+ throws RemoteException {
final int origUser = mAm.getCurrentUser();
// First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
final int testUser = createUserNoFlags();
@@ -858,6 +927,17 @@
mAm.switchUser(testUser);
}, Intent.ACTION_USER_UNLOCKED, Intent.ACTION_MEDIA_MOUNTED);
+ if (useStaticWallpaper) {
+ assertTrue(mWm.isWallpaperSupported() && mWm.isSetWallpaperAllowed());
+ try {
+ Bitmap blank = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+ mWm.setBitmap(blank, /* visibleCropHint */ null, /* allowBackup */ true,
+ /* which */ FLAG_SYSTEM | FLAG_LOCK, testUser);
+ } catch (IOException exception) {
+ fail("Unable to set static wallpaper.");
+ }
+ }
+
// Second, switch back to origUser, waiting merely for switchUser() to finish
switchUser(origUser);
attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
diff --git a/boot/Android.bp b/boot/Android.bp
index c9a3bd0..d4a6500 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -23,6 +23,18 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+soong_config_module_type {
+ name: "custom_platform_bootclasspath",
+ module_type: "platform_bootclasspath",
+ config_namespace: "AUTO",
+ bool_variables: [
+ "car_bootclasspath_fragment",
+ ],
+ properties: [
+ "fragments",
+ ],
+}
+
// This module provides access to information Soong has related to the
// whole platform bootclasspath. Currently, that information is provided solely
// through configuration but additional information will be added here.
@@ -41,7 +53,7 @@
//
// This module needs to be present in the build for the above processing to be
// done correctly.
-platform_bootclasspath {
+custom_platform_bootclasspath {
name: "platform-bootclasspath",
// The bootclasspath_fragments that contribute to the platform
@@ -127,17 +139,24 @@
apex: "com.android.wifi",
module: "com.android.wifi-bootclasspath-fragment",
},
- // only used for auto
- {
- apex: "com.android.car.framework",
- module: "com.android.car.framework-bootclasspath-fragment",
- },
{
apex: "com.android.virt",
module: "com.android.virt-bootclasspath-fragment",
},
],
+ soong_config_variables: {
+ car_bootclasspath_fragment: {
+ fragments: [
+ // only used for auto
+ {
+ apex: "com.android.car.framework",
+ module: "com.android.car.framework-bootclasspath-fragment",
+ },
+ ],
+ },
+ },
+
// Additional information needed by hidden api processing.
hidden_api: {
unsupported: [
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 6998081..ed717c4 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -195,35 +195,10 @@
return;
}
- if ("scheduling".equals(op)) {
- setSchedulingEnabled(userId);
- return;
- }
-
System.err.println("Unknown command");
showUsage();
}
- private void setSchedulingEnabled(int userId) {
- String arg = nextArg();
- if (arg == null) {
- showUsage();
- return;
- }
-
- try {
- boolean enable = Boolean.parseBoolean(arg);
- mBmgr.setFrameworkSchedulingEnabledForUser(userId, enable);
- System.out.println(
- "Backup scheduling is now "
- + (enable ? "enabled" : "disabled")
- + " for user "
- + userId);
- } catch (RemoteException e) {
- handleRemoteException(e);
- }
- }
-
private void handleRemoteException(RemoteException e) {
System.err.println(e.toString());
System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -969,7 +944,6 @@
System.err.println(" bmgr activate BOOL");
System.err.println(" bmgr activated");
System.err.println(" bmgr autorestore BOOL");
- System.err.println(" bmgr scheduling BOOL");
System.err.println("");
System.err.println("The '--user' option specifies the user on which the operation is run.");
System.err.println("It must be the first argument before the operation.");
@@ -1047,9 +1021,6 @@
System.err.println("");
System.err.println("The 'autorestore' command enables or disables automatic restore when");
System.err.println("a new package is installed.");
- System.err.println("");
- System.err.println("The 'scheduling' command enables or disables backup scheduling in the");
- System.err.println("framework.");
}
private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/core/api/current.txt b/core/api/current.txt
index ba1a358..a6446ff 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6138,7 +6138,7 @@
}
public static class Notification.Action implements android.os.Parcelable {
- ctor @Deprecated public Notification.Action(int, CharSequence, android.app.PendingIntent);
+ ctor @Deprecated public Notification.Action(int, CharSequence, @Nullable android.app.PendingIntent);
method public android.app.Notification.Action clone();
method public int describeContents();
method public boolean getAllowGeneratedReplies();
@@ -6168,8 +6168,8 @@
}
public static final class Notification.Action.Builder {
- ctor @Deprecated public Notification.Action.Builder(int, CharSequence, android.app.PendingIntent);
- ctor public Notification.Action.Builder(android.graphics.drawable.Icon, CharSequence, android.app.PendingIntent);
+ ctor @Deprecated public Notification.Action.Builder(int, CharSequence, @Nullable android.app.PendingIntent);
+ ctor public Notification.Action.Builder(android.graphics.drawable.Icon, CharSequence, @Nullable android.app.PendingIntent);
ctor public Notification.Action.Builder(android.app.Notification.Action);
method @NonNull public android.app.Notification.Action.Builder addExtras(android.os.Bundle);
method @NonNull public android.app.Notification.Action.Builder addRemoteInput(android.app.RemoteInput);
@@ -8070,6 +8070,7 @@
method public int getNetworkId();
method public boolean isEnabled();
method public boolean isFallbackToDefaultConnectionAllowed();
+ method public boolean shouldBlockNonMatchingNetworks();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PreferentialNetworkServiceConfig> CREATOR;
field public static final int PREFERENTIAL_NETWORK_ID_1 = 1; // 0x1
@@ -8087,6 +8088,7 @@
method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setFallbackToDefaultConnectionAllowed(boolean);
method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setIncludedUids(@NonNull int[]);
method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setNetworkId(int);
+ method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setShouldBlockNonMatchingNetworks(boolean);
}
public class SecurityLog {
@@ -31684,7 +31686,8 @@
method public static void scaleM(float[], int, float, float, float);
method public static void setIdentityM(float[], int);
method public static void setLookAtM(float[], int, float, float, float, float, float, float, float, float, float);
- method public static void setRotateEulerM(float[], int, float, float, float);
+ method @Deprecated public static void setRotateEulerM(float[], int, float, float, float);
+ method public static void setRotateEulerM2(@NonNull float[], int, float, float, float);
method public static void setRotateM(float[], int, float, float, float, float);
method public static void translateM(float[], int, float[], int, float, float, float);
method public static void translateM(float[], int, float, float, float);
@@ -39239,9 +39242,13 @@
method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
method @NonNull public android.service.autofill.FillResponse.Builder setFooter(@NonNull android.widget.RemoteViews);
method @NonNull public android.service.autofill.FillResponse.Builder setHeader(@NonNull android.widget.RemoteViews);
+ method @NonNull public android.service.autofill.FillResponse.Builder setIconResourceId(@DrawableRes int);
method @NonNull public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
method @NonNull public android.service.autofill.FillResponse.Builder setPresentationCancelIds(@Nullable int[]);
method @NonNull public android.service.autofill.FillResponse.Builder setSaveInfo(@NonNull android.service.autofill.SaveInfo);
+ method @NonNull public android.service.autofill.FillResponse.Builder setServiceDisplayNameResourceId(@StringRes int);
+ method @NonNull public android.service.autofill.FillResponse.Builder setShowFillDialogIcon(boolean);
+ method @NonNull public android.service.autofill.FillResponse.Builder setShowSaveDialogIcon(boolean);
method @NonNull public android.service.autofill.FillResponse.Builder setUserData(@NonNull android.service.autofill.UserData);
}
@@ -40528,11 +40535,12 @@
method public void onLaunchVoiceAssistFromKeyguard();
method public void onPrepareToShowSession(@NonNull android.os.Bundle, int);
method public void onReady();
- method public void onShowSessionFailed();
+ method public void onShowSessionFailed(@NonNull android.os.Bundle);
method public void onShutdown();
method public void setDisabledShowContext(int);
method public final void setUiHints(@NonNull android.os.Bundle);
method public void showSession(android.os.Bundle, int);
+ field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final String SERVICE_META_DATA = "android.voice_interaction";
}
@@ -50656,6 +50664,13 @@
field public int toolType;
}
+ public final class MotionPredictor {
+ ctor public MotionPredictor(@NonNull android.content.Context);
+ method public boolean isPredictionAvailable(int, int);
+ method @NonNull public java.util.List<android.view.MotionEvent> predict(long);
+ method public void record(@NonNull android.view.MotionEvent);
+ }
+
public interface OnReceiveContentListener {
method @Nullable public android.view.ContentInfo onReceiveContent(@NonNull android.view.View, @NonNull android.view.ContentInfo);
}
@@ -54835,6 +54850,7 @@
public final class InputMethodManager {
method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
+ method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
method @Nullable public android.view.inputmethod.InputMethodSubtype getCurrentInputMethodSubtype();
method @NonNull public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodList();
method @NonNull public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable android.view.inputmethod.InputMethodInfo, boolean);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f91122c..4e0e4b9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -78,6 +78,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 BIND_VISUAL_QUERY_DETECTION_SERVICE = "android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE";
field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
field public static final String BIND_WEARABLE_SENSING_SERVICE = "android.permission.BIND_WEARABLE_SENSING_SERVICE";
field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
@@ -208,6 +209,7 @@
field public static final String MIGRATE_HEALTH_CONNECT_DATA = "android.permission.MIGRATE_HEALTH_CONNECT_DATA";
field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS";
field public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING";
+ field public static final String MODIFY_AUDIO_SYSTEM_SETTINGS = "android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS";
field public static final String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS";
field public static final String MODIFY_DAY_NIGHT_MODE = "android.permission.MODIFY_DAY_NIGHT_MODE";
field @Deprecated public static final String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
@@ -6610,8 +6612,8 @@
}
public class AudioDeviceVolumeManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
- method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS}) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS}) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
}
public final class AudioFocusInfo implements android.os.Parcelable {
@@ -10404,6 +10406,7 @@
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String);
@@ -12593,11 +12596,28 @@
method public void onQueryRejected() throws java.lang.IllegalStateException;
}
+ public class VisualQueryDetector {
+ method public void destroy();
+ method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+ method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+ method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+ }
+
+ public static interface VisualQueryDetector.Callback {
+ method public void onError();
+ method public void onQueryDetected(@NonNull String);
+ method public void onQueryFinished();
+ method public void onQueryRejected();
+ method public void onVisualQueryDetectionServiceInitialized(int);
+ method public void onVisualQueryDetectionServiceRestarted();
+ }
+
public class VoiceInteractionService extends android.app.Service {
method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.service.voice.HotwordDetector.Callback);
method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
+ method @NonNull public final android.service.voice.VisualQueryDetector createVisualQueryDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VisualQueryDetector.Callback);
}
}
@@ -16226,7 +16246,6 @@
method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int, @NonNull byte[]);
method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
method public String getSmsFormat();
- method public void onMemoryAvailable(int);
method public void onReady();
method @Deprecated public final void onSendSmsResult(int, @IntRange(from=0, to=65535) int, int, int) throws java.lang.RuntimeException;
method public final void onSendSmsResultError(int, @IntRange(from=0, to=65535) int, int, int, int) throws java.lang.RuntimeException;
@@ -16635,6 +16654,14 @@
}
+package android.view.inputmethod {
+
+ public final class InputMethodManager {
+ method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull android.os.UserHandle);
+ }
+
+}
+
package android.view.translation {
public final class TranslationCapability implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0b4a708..8c64e40 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -528,6 +528,7 @@
method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, int);
@@ -897,6 +898,7 @@
method public boolean isDemo();
method public boolean isEnabled();
method public boolean isEphemeral();
+ method public boolean isForTesting();
method public boolean isFull();
method public boolean isGuest();
method public boolean isInitialized();
@@ -2025,6 +2027,7 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
@@ -3212,6 +3215,9 @@
field public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_IGNORE_VIEWS = "autofill_credential_manager_ignore_views";
field public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED = "autofill_dialog_enabled";
field public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES = "smart_suggestion_supported_modes";
+ field public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS = "non_autofillable_ime_action_ids";
+ field public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW = "package_deny_list_for_unimportant_view";
+ field public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW = "trigger_fill_request_on_unimportant_view";
}
public final class AutofillId implements android.os.Parcelable {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index dbdee07..821a23c 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -16,6 +16,8 @@
package android.accounts;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
import android.annotation.BroadcastBehavior;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -891,15 +893,24 @@
* @return An {@link AccountManagerFuture} which resolves to a Boolean, true if the account
* exists and has all of the specified features.
*/
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS_FULL)
public AccountManagerFuture<Boolean> hasFeatures(final Account account,
final String[] features,
AccountManagerCallback<Boolean> callback, Handler handler) {
+ return hasFeaturesAsUser(account, features, callback, handler, mContext.getUserId());
+ }
+
+ private AccountManagerFuture<Boolean> hasFeaturesAsUser(
+ final Account account, final String[] features,
+ AccountManagerCallback<Boolean> callback, Handler handler, int userId) {
if (account == null) throw new IllegalArgumentException("account is null");
if (features == null) throw new IllegalArgumentException("features is null");
return new Future2Task<Boolean>(handler, callback) {
@Override
public void doWork() throws RemoteException {
- mService.hasFeatures(mResponse, account, features, mContext.getOpPackageName());
+ mService.hasFeatures(
+ mResponse, account, features, userId, mContext.getOpPackageName());
}
@Override
public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
@@ -3319,7 +3330,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
public AccountManagerFuture<Bundle> finishSessionAsUser(
final Bundle sessionBundle,
final Activity activity,
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index a3a7b0c..08fb308 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -38,7 +38,7 @@
Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);
Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);
void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features,
- String opPackageName);
+ int userId, String opPackageName);
void getAccountByTypeAndFeatures(in IAccountManagerResponse response, String accountType,
in String[] features, String opPackageName);
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a81ef18..a9d14df 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -23,7 +23,6 @@
import android.content.pm.ActivityInfo.Config;
import android.content.res.ConstantState;
import android.os.Build;
-import android.util.LongArray;
import java.util.ArrayList;
@@ -547,6 +546,7 @@
*/
void skipToEndValue(boolean inReverse) {}
+
/**
* Internal use only.
*
@@ -559,36 +559,9 @@
}
/**
- * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
- * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
- * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
- * done between the two times.
+ * Internal use only.
*/
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
-
- /**
- * Internal use only. This animates any animation that has ended since lastPlayTime.
- * If an animation hasn't been finished, no change will be made.
- */
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
-
- /**
- * Internal use only. Adds all start times (after delay) to and end times to times.
- * The value must include offset.
- */
- void getStartAndEndTimes(LongArray times, long offset) {
- long startTime = offset + getStartDelay();
- if (times.indexOf(startTime) < 0) {
- times.add(startTime);
- }
- long duration = getTotalDuration();
- if (duration != DURATION_INFINITE) {
- long endTime = duration + offset;
- if (times.indexOf(endTime) < 0) {
- times.add(endTime);
- }
- }
- }
+ void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
/**
* <p>An animation listener receives notifications from an animation.
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 257adfe..bc8db02 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -23,11 +23,9 @@
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.LongArray;
import android.view.animation.Animation;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
@@ -183,16 +181,6 @@
*/
private long mPauseTime = -1;
- /**
- * The start and stop times of all descendant animators.
- */
- private long[] mChildStartAndStopTimes;
-
- /**
- * Tracks whether we've notified listeners of the onAnimationStart() event.
- */
- private boolean mStartListenersCalled;
-
// This is to work around a bug in b/34736819. This needs to be removed once app team
// fixes their side.
private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -741,7 +729,14 @@
startAnimation();
}
- notifyStartListeners(inReverse);
+ if (mListeners != null) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onAnimationStart(this, inReverse);
+ }
+ }
if (isEmptySet) {
// In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
// onAnimationEnd() right away.
@@ -749,32 +744,6 @@
}
}
- private void notifyStartListeners(boolean inReverse) {
- if (mListeners != null && !mStartListenersCalled) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- AnimatorListener listener = tmpListeners.get(i);
- listener.onAnimationStart(this, inReverse);
- }
- }
- mStartListenersCalled = true;
- }
-
- private void notifyEndListeners(boolean inReverse) {
- if (mListeners != null && mStartListenersCalled) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- AnimatorListener listener = tmpListeners.get(i);
- listener.onAnimationEnd(this, inReverse);
- }
- }
- mStartListenersCalled = false;
- }
-
// Returns true if set is empty or contains nothing but animator sets with no start delay.
private static boolean isEmptySet(AnimatorSet set) {
if (set.getStartDelay() > 0) {
@@ -810,25 +779,26 @@
@Override
void skipToEndValue(boolean inReverse) {
+ if (!isInitialized()) {
+ throw new UnsupportedOperationException("Children must be initialized.");
+ }
+
// This makes sure the animation events are sorted an up to date.
initAnimation();
- initChildren();
// Calling skip to the end in the sequence that they would be called in a forward/reverse
// run, such that the sequential animations modifying the same property would have
// the right value in the end.
if (inReverse) {
for (int i = mEvents.size() - 1; i >= 0; i--) {
- AnimationEvent event = mEvents.get(i);
- if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- event.mNode.mAnimation.skipToEndValue(true);
+ if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
}
}
} else {
for (int i = 0; i < mEvents.size(); i++) {
- AnimationEvent event = mEvents.get(i);
- if (event.mEvent == AnimationEvent.ANIMATION_END) {
- event.mNode.mAnimation.skipToEndValue(false);
+ if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
+ mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
}
}
}
@@ -844,216 +814,72 @@
* {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
* as needed, based on the last play time and current play time.
*/
- private void animateBasedOnPlayTime(
- long currentPlayTime,
- long lastPlayTime,
- boolean inReverse,
- boolean notify
- ) {
- if (currentPlayTime < 0 || lastPlayTime < -1) {
+ @Override
+ void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+ if (currentPlayTime < 0 || lastPlayTime < 0) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
}
// TODO: take into account repeat counts and repeat callback when repeat is implemented.
+ // Clamp currentPlayTime and lastPlayTime
+ // TODO: Make this more efficient
+
+ // Convert the play times to the forward direction.
if (inReverse) {
- long duration = getTotalDuration();
- if (duration == DURATION_INFINITE) {
- throw new UnsupportedOperationException(
- "Cannot reverse AnimatorSet with infinite duration"
- );
+ if (getTotalDuration() == DURATION_INFINITE) {
+ throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
+ + " duration");
}
- // Convert the play times to the forward direction.
+ long duration = getTotalDuration() - mStartDelay;
currentPlayTime = Math.min(currentPlayTime, duration);
currentPlayTime = duration - currentPlayTime;
lastPlayTime = duration - lastPlayTime;
+ inReverse = false;
}
- long[] startEndTimes = ensureChildStartAndEndTimes();
- int index = findNextIndex(lastPlayTime, startEndTimes);
- int endIndex = findNextIndex(currentPlayTime, startEndTimes);
-
- // Change values at the start/end times so that values are set in the right order.
- // We don't want an animator that would finish before another to override the value
- // set by another animator that finishes earlier.
- if (currentPlayTime >= lastPlayTime) {
- while (index < endIndex) {
- long playTime = startEndTimes[index];
- if (lastPlayTime != playTime) {
- animateSkipToEnds(playTime, lastPlayTime, notify);
- animateValuesInRange(playTime, lastPlayTime, notify);
- lastPlayTime = playTime;
- }
- index++;
- }
- } else {
- while (index > endIndex) {
- index--;
- long playTime = startEndTimes[index];
- if (lastPlayTime != playTime) {
- animateSkipToEnds(playTime, lastPlayTime, notify);
- animateValuesInRange(playTime, lastPlayTime, notify);
- lastPlayTime = playTime;
- }
- }
- }
- if (currentPlayTime != lastPlayTime) {
- animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
- animateValuesInRange(currentPlayTime, lastPlayTime, notify);
- }
- }
-
- /**
- * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
- * is returned. Otherwise, it returns the index at which it would be placed if it were
- * to be inserted.
- */
- private int findNextIndex(long playTime, long[] startEndTimes) {
- int index = Arrays.binarySearch(startEndTimes, playTime);
- if (index < 0) {
- index = -index - 1;
- } else {
- index++;
- }
- return index;
- }
-
- @Override
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
- initAnimation();
-
- if (lastPlayTime > currentPlayTime) {
- if (notify) {
- notifyStartListeners(true);
- }
- for (int i = mEvents.size() - 1; i >= 0; i--) {
- AnimationEvent event = mEvents.get(i);
- Node node = event.mNode;
- if (event.mEvent == AnimationEvent.ANIMATION_END
- && node.mStartTime != DURATION_INFINITE
- ) {
- Animator animator = node.mAnimation;
- long start = node.mStartTime;
- long end = node.mTotalDuration == DURATION_INFINITE
- ? Long.MAX_VALUE : node.mEndTime;
- if (currentPlayTime <= start && start < lastPlayTime) {
- animator.animateSkipToEnds(
- 0,
- lastPlayTime - node.mStartTime,
- notify
- );
- } else if (start <= currentPlayTime && currentPlayTime <= end) {
- animator.animateSkipToEnds(
- currentPlayTime - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
- );
- }
- }
- }
- if (currentPlayTime <= 0 && notify) {
- notifyEndListeners(true);
- }
- } else {
- if (notify) {
- notifyStartListeners(false);
- }
- int eventsSize = mEvents.size();
- for (int i = 0; i < eventsSize; i++) {
- AnimationEvent event = mEvents.get(i);
- Node node = event.mNode;
- if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
- && node.mStartTime != DURATION_INFINITE
- ) {
- Animator animator = node.mAnimation;
- long start = node.mStartTime;
- long end = node.mTotalDuration == DURATION_INFINITE
- ? Long.MAX_VALUE : node.mEndTime;
- if (lastPlayTime < end && end <= currentPlayTime) {
- animator.animateSkipToEnds(
- end - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
- );
- } else if (start <= currentPlayTime && currentPlayTime <= end) {
- animator.animateSkipToEnds(
- currentPlayTime - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
- );
- }
- }
- }
- if (currentPlayTime >= getTotalDuration() && notify) {
- notifyEndListeners(false);
- }
- }
- }
-
- @Override
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
- initAnimation();
-
- if (notify) {
- if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
- notifyStartListeners(false);
- } else {
- long duration = getTotalDuration();
- if (duration >= 0
- && (lastPlayTime > duration || (lastPlayTime == duration
- && currentPlayTime < duration))
- ) {
- notifyStartListeners(true);
- }
- }
- }
-
- int eventsSize = mEvents.size();
- for (int i = 0; i < eventsSize; i++) {
+ ArrayList<Node> unfinishedNodes = new ArrayList<>();
+ // Assumes forward playing from here on.
+ for (int i = 0; i < mEvents.size(); i++) {
AnimationEvent event = mEvents.get(i);
- Node node = event.mNode;
- if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
- && node.mStartTime != DURATION_INFINITE
- ) {
- Animator animator = node.mAnimation;
- long start = node.mStartTime;
- long end = node.mTotalDuration == DURATION_INFINITE
- ? Long.MAX_VALUE : node.mEndTime;
- if ((start < currentPlayTime && currentPlayTime < end)
- || (start == currentPlayTime && lastPlayTime < start)
- || (end == currentPlayTime && lastPlayTime > end)
- ) {
- animator.animateValuesInRange(
- currentPlayTime - node.mStartTime,
- Math.max(-1, lastPlayTime - node.mStartTime),
- notify
- );
+ if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
+ break;
+ }
+
+ // This animation started prior to the current play time, and won't finish before the
+ // play time, add to the unfinished list.
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ if (event.mNode.mEndTime == DURATION_INFINITE
+ || event.mNode.mEndTime > currentPlayTime) {
+ unfinishedNodes.add(event.mNode);
}
}
- }
- }
-
- private long[] ensureChildStartAndEndTimes() {
- if (mChildStartAndStopTimes == null) {
- LongArray startAndEndTimes = new LongArray();
- getStartAndEndTimes(startAndEndTimes, 0);
- long[] times = startAndEndTimes.toArray();
- Arrays.sort(times);
- mChildStartAndStopTimes = times;
- }
- return mChildStartAndStopTimes;
- }
-
- @Override
- void getStartAndEndTimes(LongArray times, long offset) {
- int eventsSize = mEvents.size();
- for (int i = 0; i < eventsSize; i++) {
- AnimationEvent event = mEvents.get(i);
- if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
- && event.mNode.mStartTime != DURATION_INFINITE
- ) {
- event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
+ // For animations that do finish before the play time, end them in the sequence that
+ // they would in a normal run.
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ // Skip to the end of the animation.
+ event.mNode.mAnimation.skipToEndValue(false);
}
}
+
+ // Seek unfinished animation to the right time.
+ for (int i = 0; i < unfinishedNodes.size(); i++) {
+ Node node = unfinishedNodes.get(i);
+ long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
+ if (!inReverse) {
+ playTime -= node.mAnimation.getStartDelay();
+ }
+ node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
+ }
+
+ // Seek not yet started animations.
+ for (int i = 0; i < mEvents.size(); i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.getTime() > currentPlayTime
+ && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ event.mNode.mAnimation.skipToEndValue(true);
+ }
+ }
+
}
@Override
@@ -1073,6 +899,10 @@
return mChildrenInitialized;
}
+ private void skipToStartValue(boolean inReverse) {
+ skipToEndValue(!inReverse);
+ }
+
/**
* Sets the position of the animation to the specified point in time. This time should
* be between 0 and the total duration of the animation, including any repetition. If
@@ -1080,11 +910,6 @@
* set to this time; it will simply set the time to this value and perform any appropriate
* actions based on that time. If the animation is already running, then setCurrentPlayTime()
* will set the current playing time to this value and continue playing from that point.
- * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
- * that hasn't been {@link #start()}ed, will issue
- * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
- * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
- * events.
*
* @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
* Unless the animation is reversing, the playtime is considered the time since
@@ -1101,27 +926,29 @@
if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
|| playTime < 0) {
throw new UnsupportedOperationException("Error: Play time should always be in between"
- + " 0 and duration.");
+ + "0 and duration.");
}
initAnimation();
- long lastPlayTime = mSeekState.getPlayTime();
if (!isStarted() || isPaused()) {
- if (mReversing && !isStarted()) {
+ if (mReversing) {
throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
+ " should not be set when AnimatorSet is not started.");
}
if (!mSeekState.isActive()) {
findLatestEventIdForTime(0);
- initChildren();
// Set all the values to start values.
- skipToEndValue(!mReversing);
+ initChildren();
mSeekState.setPlayTime(0, mReversing);
}
+ animateBasedOnPlayTime(playTime, 0, mReversing);
+ mSeekState.setPlayTime(playTime, mReversing);
+ } else {
+ // If the animation is running, just set the seek time and wait until the next frame
+ // (i.e. doAnimationFrame(...)) to advance the animation.
+ mSeekState.setPlayTime(playTime, mReversing);
}
- animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
- mSeekState.setPlayTime(playTime, mReversing);
}
/**
@@ -1154,16 +981,10 @@
private void initChildren() {
if (!isInitialized()) {
mChildrenInitialized = true;
-
- // We have to initialize all the start values so that they are based on the previous
- // values.
- long[] times = ensureChildStartAndEndTimes();
-
- long previousTime = -1;
- for (long time : times) {
- animateBasedOnPlayTime(time, previousTime, false, false);
- previousTime = time;
- }
+ // Forcefully initialize all children based on their end time, so that if the start
+ // value of a child is dependent on a previous animation, the animation will be
+ // initialized after the the previous animations have been advanced to the end.
+ skipToEndValue(false);
}
}
@@ -1237,7 +1058,7 @@
for (int i = 0; i < mPlayingSet.size(); i++) {
Node node = mPlayingSet.get(i);
if (!node.mEnded) {
- pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
+ pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
}
}
@@ -1308,7 +1129,7 @@
pulseFrame(node, 0);
} else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
// end event:
- pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+ pulseFrame(node, getPlayTimeForNode(playTime, node));
}
}
} else {
@@ -1329,7 +1150,7 @@
pulseFrame(node, 0);
} else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
// start event:
- pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+ pulseFrame(node, getPlayTimeForNode(playTime, node));
}
}
}
@@ -1351,15 +1172,11 @@
}
}
- private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
- return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
+ private long getPlayTimeForNode(long overallPlayTime, Node node) {
+ return getPlayTimeForNode(overallPlayTime, node, mReversing);
}
- private long getPlayTimeForNodeIncludingDelay(
- long overallPlayTime,
- Node node,
- boolean inReverse
- ) {
+ private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
if (inReverse) {
overallPlayTime = getTotalDuration() - overallPlayTime;
return node.mEndTime - overallPlayTime;
@@ -1381,8 +1198,26 @@
}
// Set the child animators to the right end:
if (mShouldResetValuesAtStart) {
- initChildren();
- skipToEndValue(!mReversing);
+ if (isInitialized()) {
+ skipToEndValue(!mReversing);
+ } else if (mReversing) {
+ // Reversing but haven't initialized all the children yet.
+ initChildren();
+ skipToEndValue(!mReversing);
+ } else {
+ // If not all children are initialized and play direction is forward
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ Animator anim = mEvents.get(i).mNode.mAnimation;
+ // Only reset the animations that have been initialized to start value,
+ // so that if they are defined without a start value, they will get the
+ // values set at the right time (i.e. the next animation run)
+ if (anim.isInitialized()) {
+ anim.skipToEndValue(true);
+ }
+ }
+ }
+ }
}
if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
@@ -1457,7 +1292,15 @@
// No longer receive callbacks
removeAnimationCallback();
- notifyEndListeners(mReversing);
+ // Call end listener
+ if (mListeners != null) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onAnimationEnd(this, mReversing);
+ }
+ }
removeAnimationEndListener();
mSelfPulse = true;
mReversing = false;
@@ -2079,11 +1922,11 @@
}
void setPlayTime(long playTime, boolean inReverse) {
+ // TODO: This can be simplified.
+
// Clamp the play time
if (getTotalDuration() != DURATION_INFINITE) {
mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
- } else {
- mPlayTime = playTime;
}
mPlayTime = Math.max(0, mPlayTime);
mSeekingInReverse = inReverse;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 7009725..6ab7ae6 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -324,9 +324,8 @@
listenerCopy = new ArrayList<>(sDurationScaleChangeListeners);
}
- int listenersSize = listenerCopy.size();
- for (int i = 0; i < listenersSize; i++) {
- final DurationScaleChangeListener listener = listenerCopy.get(i).get();
+ for (WeakReference<DurationScaleChangeListener> listenerRef : listenerCopy) {
+ final DurationScaleChangeListener listener = listenerRef.get();
if (listener != null) {
listener.onChanged(durationScale);
}
@@ -625,7 +624,7 @@
public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
- mValuesMap = new HashMap<>(numValues);
+ mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
@@ -659,11 +658,9 @@
@CallSuper
void initAnimation() {
if (!mInitialized) {
- if (mValues != null) {
- int numValues = mValues.length;
- for (int i = 0; i < numValues; ++i) {
- mValues[i].init();
- }
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].init();
}
mInitialized = true;
}
@@ -1108,30 +1105,18 @@
}
}
- private void notifyStartListeners(boolean isReversing) {
+ private void notifyStartListeners() {
if (mListeners != null && !mStartListenersCalled) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this, isReversing);
+ tmpListeners.get(i).onAnimationStart(this, mReversing);
}
}
mStartListenersCalled = true;
}
- private void notifyEndListeners(boolean isReversing) {
- if (mListeners != null && mStartListenersCalled) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this, isReversing);
- }
- }
- mStartListenersCalled = false;
- }
-
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
@@ -1222,16 +1207,12 @@
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
- notifyStartListeners(mReversing);
+ notifyStartListeners();
}
- int listenersSize = mListeners.size();
- if (listenersSize > 0) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- for (int i = 0; i < listenersSize; i++) {
- AnimatorListener listener = tmpListeners.get(i);
- listener.onAnimationCancel(this);
- }
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ for (AnimatorListener listener : tmpListeners) {
+ listener.onAnimationCancel(this);
}
}
endAnimation();
@@ -1336,14 +1317,22 @@
boolean notify = (mStarted || mRunning) && mListeners != null;
if (notify && !mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
- notifyStartListeners(mReversing);
+ notifyStartListeners();
}
mRunning = false;
mStarted = false;
+ mStartListenersCalled = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
- notifyEndListeners(mReversing);
+ if (notify && mListeners != null) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onAnimationEnd(this, mReversing);
+ }
+ }
// mReversing needs to be reset *after* notifying the listeners for the end callbacks.
mReversing = false;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -1370,8 +1359,9 @@
} else {
mOverallFraction = 0f;
}
-
- notifyStartListeners(mReversing);
+ if (mListeners != null) {
+ notifyStartListeners();
+ }
}
/**
@@ -1462,32 +1452,16 @@
* will be called.
*/
@Override
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
- if (currentPlayTime < 0 || lastPlayTime < -1) {
+ void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+ if (currentPlayTime < 0 || lastPlayTime < 0) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
}
initAnimation();
- long duration = getTotalDuration();
- if (notify) {
- if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
- notifyStartListeners(false);
- } else if (lastPlayTime > duration
- || (lastPlayTime == duration && currentPlayTime < duration)
- ) {
- notifyStartListeners(true);
- }
- }
- if (duration >= 0) {
- lastPlayTime = Math.min(duration, lastPlayTime);
- }
- lastPlayTime -= mStartDelay;
- currentPlayTime -= mStartDelay;
-
// Check whether repeat callback is needed only when repeat count is non-zero
if (mRepeatCount > 0) {
- int iteration = Math.max(0, (int) (currentPlayTime / mDuration));
- int lastIteration = Math.max(0, (int) (lastPlayTime / mDuration));
+ int iteration = (int) (currentPlayTime / mDuration);
+ int lastIteration = (int) (lastPlayTime / mDuration);
// Clamp iteration to [0, mRepeatCount]
iteration = Math.min(iteration, mRepeatCount);
@@ -1503,37 +1477,16 @@
}
}
- if (mRepeatCount != INFINITE && currentPlayTime > (mRepeatCount + 1) * mDuration) {
- throw new IllegalStateException("Can't animate a value outside of the duration");
+ if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
+ skipToEndValue(inReverse);
} else {
// Find the current fraction:
- float fraction = Math.max(0, currentPlayTime) / (float) mDuration;
- fraction = getCurrentIterationFraction(fraction, false);
+ float fraction = currentPlayTime / (float) mDuration;
+ fraction = getCurrentIterationFraction(fraction, inReverse);
animateValue(fraction);
}
}
- @Override
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
- boolean inReverse = currentPlayTime < lastPlayTime;
- boolean doSkip;
- if (currentPlayTime <= 0 && lastPlayTime > 0) {
- doSkip = true;
- } else {
- long duration = getTotalDuration();
- doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
- }
- if (doSkip) {
- if (notify) {
- notifyStartListeners(inReverse);
- }
- skipToEndValue(inReverse);
- if (notify) {
- notifyEndListeners(inReverse);
- }
- }
- }
-
/**
* Internal use only.
* Skips the animation value to end/start, depending on whether the play direction is forward
@@ -1688,9 +1641,6 @@
Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
(int) (fraction * 1000));
}
- if (mValues == null) {
- return;
- }
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 174c982..b626493 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -404,6 +404,13 @@
public static final int START_FLAG_NATIVE_DEBUGGING = 1<<3;
/**
+ * Flag for IActivityManaqer.startActivity: launch the app for
+ * debugging and suspend threads.
+ * @hide
+ */
+ public static final int START_FLAG_DEBUG_SUSPEND = 1 << 4;
+
+ /**
* Result for IActivityManaqer.broadcastIntent: success!
* @hide
*/
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 421ba7c..3761251 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -784,9 +784,10 @@
static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
- boolean ordered, boolean sticky, IBinder token, int sendingUser) {
+ boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
+ int sendingUser) {
super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
- token, sendingUser, intent.getFlags());
+ assumeDelivered, token, sendingUser, intent.getFlags());
this.intent = intent;
}
@@ -1040,10 +1041,10 @@
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
- boolean sync, int sendingUser, int processState) {
+ boolean ordered, boolean assumeDelivered, int sendingUser, int processState) {
updateProcessState(processState, false);
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
- sync, false, mAppThread.asBinder(), sendingUser);
+ ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser);
r.info = info;
sendMessage(H.RECEIVER, r);
}
@@ -1054,11 +1055,11 @@
if (r.registered) {
scheduleRegisteredReceiver(r.receiver, r.intent,
r.resultCode, r.data, r.extras, r.ordered, r.sticky,
- r.sendingUser, r.processState);
+ r.assumeDelivered, r.sendingUser, r.processState);
} else {
scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
r.resultCode, r.data, r.extras, r.sync,
- r.sendingUser, r.processState);
+ r.assumeDelivered, r.sendingUser, r.processState);
}
}
}
@@ -1288,10 +1289,25 @@
// applies transaction ordering per object for such calls.
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser, int processState) throws RemoteException {
+ boolean sticky, boolean assumeDelivered, int sendingUser, int processState)
+ throws RemoteException {
updateProcessState(processState, false);
- receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
- sticky, sendingUser);
+
+ // We can't modify IIntentReceiver due to UnsupportedAppUsage, so
+ // try our best to shortcut to known subclasses, and alert if
+ // registered using a custom IIntentReceiver that isn't able to
+ // report an expected delivery event
+ if (receiver instanceof LoadedApk.ReceiverDispatcher.InnerReceiver) {
+ ((LoadedApk.ReceiverDispatcher.InnerReceiver) receiver).performReceive(intent,
+ resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser);
+ } else {
+ if (!assumeDelivered) {
+ Log.wtf(TAG, "scheduleRegisteredReceiver() called for " + receiver
+ + " and " + intent + " without mechanism to finish delivery");
+ }
+ receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
+ sendingUser);
+ }
}
@Override
@@ -6869,26 +6885,13 @@
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
- // Wait for debugger after we have notified the system to finish attach application
if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
- Slog.w(TAG, "Application " + data.info.getPackageName()
- + " is waiting for the debugger ...");
-
- try {
- mgr.showWaitingForDebugger(mAppThread, true);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
-
- Debug.waitForDebugger();
-
- try {
- mgr.showWaitingForDebugger(mAppThread, false);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ waitForDebugger(data);
+ } else if (data.debugMode == ApplicationThreadConstants.DEBUG_SUSPEND) {
+ suspendAllAndSendVmStart(data);
}
+ // Nothing special to do in case of DEBUG_ON.
}
try {
@@ -6972,6 +6975,49 @@
}
}
+ @UnsupportedAppUsage
+ private void waitForDebugger(AppBindData data) {
+ final IActivityManager mgr = ActivityManager.getService();
+ Slog.w(TAG, "Application " + data.info.getPackageName()
+ + " is waiting for the debugger ...");
+
+ try {
+ mgr.showWaitingForDebugger(mAppThread, true);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
+ Debug.waitForDebugger();
+
+ try {
+ mgr.showWaitingForDebugger(mAppThread, false);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ private void suspendAllAndSendVmStart(AppBindData data) {
+ final IActivityManager mgr = ActivityManager.getService();
+ Slog.w(TAG, "Application " + data.info.getPackageName()
+ + " is suspending. Debugger needs to resume to continue.");
+
+ try {
+ mgr.showWaitingForDebugger(mAppThread, true);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
+ Debug.suspendAllAndSendVmStart();
+
+ try {
+ mgr.showWaitingForDebugger(mAppThread, false);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+
private void handleSetContentCaptureOptionsCallback(String packageName) {
if (mContentCaptureOptionsCallback != null) {
return;
diff --git a/core/java/android/app/ApplicationThreadConstants.java b/core/java/android/app/ApplicationThreadConstants.java
index 1fa670f..c7620c3 100644
--- a/core/java/android/app/ApplicationThreadConstants.java
+++ b/core/java/android/app/ApplicationThreadConstants.java
@@ -28,6 +28,7 @@
public static final int DEBUG_OFF = 0;
public static final int DEBUG_ON = 1;
public static final int DEBUG_WAIT = 2;
+ public static final int DEBUG_SUSPEND = 3;
// the package has been removed, clean up internal references
public static final int PACKAGE_REMOVED = 0;
@@ -36,4 +37,4 @@
public static final int PACKAGE_REMOVED_DONT_KILL = 2;
// a previously removed package was replaced with a new version [eg. upgrade, split added, ...]
public static final int PACKAGE_REPLACED = 3;
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 3984fee..4f69d85 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -65,8 +65,8 @@
oneway interface IApplicationThread {
void scheduleReceiver(in Intent intent, in ActivityInfo info,
in CompatibilityInfo compatInfo,
- int resultCode, in String data, in Bundle extras, boolean sync,
- int sendingUser, int processState);
+ int resultCode, in String data, in Bundle extras, boolean ordered,
+ boolean assumeDelivered, int sendingUser, int processState);
void scheduleReceiverList(in List<ReceiverInfo> info);
@@ -102,7 +102,7 @@
in String[] args);
void scheduleRegisteredReceiver(IIntentReceiver receiver, in Intent intent,
int resultCode, in String data, in Bundle extras, boolean ordered,
- boolean sticky, int sendingUser, int processState);
+ boolean sticky, boolean assumeDelivered, int sendingUser, int processState);
void scheduleLowMemory();
void profilerControl(boolean start, in ProfilerInfo profilerInfo, int profileType);
void setSchedulingGroup(int group);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 3620a60..7c22902 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1679,6 +1679,16 @@
@Override
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+ Log.wtf(TAG, "performReceive() called targeting raw IIntentReceiver for " + intent);
+ performReceive(intent, resultCode, data, extras, ordered, sticky,
+ BroadcastReceiver.PendingResult.guessAssumeDelivered(
+ BroadcastReceiver.PendingResult.TYPE_REGISTERED, ordered),
+ sendingUser);
+ }
+
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
+ int sendingUser) {
final LoadedApk.ReceiverDispatcher rd;
if (intent == null) {
Log.wtf(TAG, "Null intent received");
@@ -1693,8 +1703,8 @@
}
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
- ordered, sticky, sendingUser);
- } else {
+ ordered, sticky, assumeDelivered, sendingUser);
+ } else if (!assumeDelivered) {
// The activity manager dispatched a broadcast to a registered
// receiver in this process, but before it could be delivered the
// receiver was unregistered. Acknowledge the broadcast on its
@@ -1729,30 +1739,26 @@
final class Args extends BroadcastReceiver.PendingResult {
private Intent mCurIntent;
- private final boolean mOrdered;
private boolean mDispatched;
private boolean mRunCalled;
public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
- boolean ordered, boolean sticky, int sendingUser) {
+ boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser) {
super(resultCode, resultData, resultExtras,
mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
- sticky, mAppThread.asBinder(), sendingUser, intent.getFlags());
+ sticky, assumeDelivered, mAppThread.asBinder(), sendingUser,
+ intent.getFlags());
mCurIntent = intent;
- mOrdered = ordered;
}
public final Runnable getRunnable() {
return () -> {
final BroadcastReceiver receiver = mReceiver;
- final boolean ordered = mOrdered;
if (ActivityThread.DEBUG_BROADCAST) {
int seq = mCurIntent.getIntExtra("seq", -1);
Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
+ " seq=" + seq + " to " + mReceiver);
- Slog.i(ActivityThread.TAG, " mRegistered=" + mRegistered
- + " mOrderedHint=" + ordered);
}
final IActivityManager mgr = ActivityManager.getService();
@@ -1766,11 +1772,9 @@
mDispatched = true;
mRunCalled = true;
if (receiver == null || intent == null || mForgotten) {
- if (mRegistered && ordered) {
- if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
- "Finishing null broadcast to " + mReceiver);
- sendFinished(mgr);
- }
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing null broadcast to " + mReceiver);
+ sendFinished(mgr);
return;
}
@@ -1790,11 +1794,9 @@
receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);
} catch (Exception e) {
- if (mRegistered && ordered) {
- if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
- "Finishing failed broadcast to " + mReceiver);
- sendFinished(mgr);
- }
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing failed broadcast to " + mReceiver);
+ sendFinished(mgr);
if (mInstrumentation == null ||
!mInstrumentation.onException(mReceiver, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -1868,9 +1870,10 @@
}
public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+ Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
+ int sendingUser) {
final Args args = new Args(intent, resultCode, data, extras, ordered,
- sticky, sendingUser);
+ sticky, assumeDelivered, sendingUser);
if (intent == null) {
Log.wtf(TAG, "Null intent received");
} else {
@@ -1881,12 +1884,10 @@
}
}
if (intent == null || !mActivityThread.post(args.getRunnable())) {
- if (mRegistered && ordered) {
- IActivityManager mgr = ActivityManager.getService();
- if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
- "Finishing sync broadcast to " + mReceiver);
- args.sendFinished(mgr);
- }
+ IActivityManager mgr = ActivityManager.getService();
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing sync broadcast to " + mReceiver);
+ args.sendFinished(mgr);
}
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f9ef3cc..c081d82 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1782,7 +1782,7 @@
* @deprecated Use {@link android.app.Notification.Action.Builder}.
*/
@Deprecated
- public Action(int icon, CharSequence title, PendingIntent intent) {
+ public Action(int icon, CharSequence title, @Nullable PendingIntent intent) {
this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */);
}
@@ -1907,10 +1907,12 @@
* which may display them in other contexts, for example on a wearable device.
* @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
+ * @param intent the {@link PendingIntent} to fire when users trigger this action. May
+ * be null, in which case the action may be rendered in a disabled presentation by the
+ * system UI.
*/
@Deprecated
- public Builder(int icon, CharSequence title, PendingIntent intent) {
+ public Builder(int icon, CharSequence title, @Nullable PendingIntent intent) {
this(Icon.createWithResource("", icon), title, intent);
}
@@ -1939,9 +1941,11 @@
*
* @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
+ * @param intent the {@link PendingIntent} to fire when users trigger this action. May
+ * be null, in which case the action may be rendered in a disabled presentation by the
+ * system UI.
*/
- public Builder(Icon icon, CharSequence title, PendingIntent intent) {
+ public Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent) {
this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false);
}
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
index d90eee7..8d7e3c4 100644
--- a/core/java/android/app/ReceiverInfo.aidl
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -34,6 +34,7 @@
Intent intent;
String data;
Bundle extras;
+ boolean assumeDelivered;
int sendingUser;
int processState;
int resultCode;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1633073..33721a0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
@@ -4186,7 +4187,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
public boolean packageHasActiveAdmins(String packageName) {
return packageHasActiveAdmins(packageName, myUserId());
}
@@ -8743,7 +8744,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_DEVICE_ADMINS,
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ INTERACT_ACROSS_USERS_FULL
})
public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing,
int userHandle) {
@@ -10654,7 +10655,7 @@
*/
@UserHandleAware
@RequiresPermission(allOf = {
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ INTERACT_ACROSS_USERS_FULL,
android.Manifest.permission.MANAGE_USERS
}, conditional = true)
public @Nullable List<String> getPermittedInputMethods() {
@@ -14845,7 +14846,7 @@
* @hide
*/
@RequiresPermission(anyOf = {
- permission.INTERACT_ACROSS_USERS_FULL,
+ INTERACT_ACROSS_USERS_FULL,
permission.INTERACT_ACROSS_USERS
}, conditional = true)
public boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) {
@@ -14877,7 +14878,7 @@
* @hide
*/
@RequiresPermission(anyOf = {
- permission.INTERACT_ACROSS_USERS_FULL,
+ INTERACT_ACROSS_USERS_FULL,
permission.INTERACT_ACROSS_USERS
})
public @Nullable Set<String> getCrossProfileCalendarPackages() {
@@ -14970,7 +14971,7 @@
* @hide
*/
@RequiresPermission(anyOf = {
- permission.INTERACT_ACROSS_USERS_FULL,
+ INTERACT_ACROSS_USERS_FULL,
permission.INTERACT_ACROSS_USERS,
permission.INTERACT_ACROSS_PROFILES
})
@@ -16154,6 +16155,23 @@
}
/**
+ * Reset cache for {@link #shouldAllowBypassingDevicePolicyManagementRoleQualification}.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS)
+ public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
+ if (mService != null) {
+ try {
+ mService.resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* @return {@code true} if bypassing the device policy management role qualification is allowed
* with the current state of the device.
*
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 20695ca..aebeaf0 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -575,6 +575,7 @@
void resetStrings(in List<String> stringIds);
ParcelableResource getString(String stringId);
+ void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState();
boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle);
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
index b0ea499..0513099 100644
--- a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
@@ -51,6 +51,7 @@
final boolean mIsEnabled;
final int mNetworkId;
final boolean mAllowFallbackToDefaultConnection;
+ final boolean mShouldBlockNonMatchingNetworks;
final int[] mIncludedUids;
final int[] mExcludedUids;
@@ -64,6 +65,8 @@
"preferential_network_service_network_id";
private static final String TAG_ALLOW_FALLBACK_TO_DEFAULT_CONNECTION =
"allow_fallback_to_default_connection";
+ private static final String TAG_BLOCK_NON_MATCHING_NETWORKS =
+ "block_non_matching_networks";
private static final String TAG_INCLUDED_UIDS = "included_uids";
private static final String TAG_EXCLUDED_UIDS = "excluded_uids";
private static final String ATTR_VALUE = "value";
@@ -111,10 +114,12 @@
}
private PreferentialNetworkServiceConfig(boolean isEnabled,
- boolean allowFallbackToDefaultConnection, int[] includedUids,
+ boolean allowFallbackToDefaultConnection, boolean shouldBlockNonMatchingNetworks,
+ int[] includedUids,
int[] excludedUids, @PreferentialNetworkPreferenceId int networkId) {
mIsEnabled = isEnabled;
mAllowFallbackToDefaultConnection = allowFallbackToDefaultConnection;
+ mShouldBlockNonMatchingNetworks = shouldBlockNonMatchingNetworks;
mIncludedUids = includedUids;
mExcludedUids = excludedUids;
mNetworkId = networkId;
@@ -123,6 +128,7 @@
private PreferentialNetworkServiceConfig(Parcel in) {
mIsEnabled = in.readBoolean();
mAllowFallbackToDefaultConnection = in.readBoolean();
+ mShouldBlockNonMatchingNetworks = in.readBoolean();
mNetworkId = in.readInt();
mIncludedUids = in.createIntArray();
mExcludedUids = in.createIntArray();
@@ -137,9 +143,18 @@
}
/**
- * is fallback to default network allowed. This boolean configures whether default connection
- * (default internet or wifi) should be used or not if a preferential network service
- * connection is not available.
+ * Whether fallback to the device-wide default network is allowed.
+ *
+ * This boolean configures whether the default connection (e.g. general cell network or wifi)
+ * should be used if no preferential network service connection is available. If true, the
+ * default connection will be used when no preferential service is available. If false, the
+ * UIDs subject to this configuration will have no default network.
+ * Note that while this boolean determines whether the UIDs subject to this configuration have
+ * a default network in the absence of a preferential service, apps can still explicitly decide
+ * to use another network than their default network by requesting them from the system. This
+ * boolean does not determine whether the UIDs are blocked from using such other networks.
+ * See {@link #shouldBlockNonMatchingNetworks()} for that configuration.
+ *
* @return true if fallback is allowed, else false.
*/
public boolean isFallbackToDefaultConnectionAllowed() {
@@ -147,6 +162,21 @@
}
/**
+ * Whether to block UIDs from using other networks than the preferential service.
+ *
+ * Apps can inspect the list of available networks on the device and choose to use multiple
+ * of them concurrently for performance, privacy or other reasons.
+ * This boolean configures whether the concerned UIDs should be blocked from using
+ * networks that do not match the configured preferential network service even if these
+ * networks are otherwise open to all apps.
+ *
+ * @return true if UIDs should be blocked from using the other networks, else false.
+ */
+ public boolean shouldBlockNonMatchingNetworks() {
+ return mShouldBlockNonMatchingNetworks;
+ }
+
+ /**
* Get the array of uids that are applicable for the profile preference.
*
* {@see #getExcludedUids()}
@@ -190,6 +220,7 @@
return "PreferentialNetworkServiceConfig{"
+ "mIsEnabled=" + isEnabled()
+ "mAllowFallbackToDefaultConnection=" + isFallbackToDefaultConnectionAllowed()
+ + "mBlockNonMatchingNetworks=" + shouldBlockNonMatchingNetworks()
+ "mIncludedUids=" + Arrays.toString(mIncludedUids)
+ "mExcludedUids=" + Arrays.toString(mExcludedUids)
+ "mNetworkId=" + mNetworkId
@@ -203,6 +234,7 @@
final PreferentialNetworkServiceConfig that = (PreferentialNetworkServiceConfig) o;
return mIsEnabled == that.mIsEnabled
&& mAllowFallbackToDefaultConnection == that.mAllowFallbackToDefaultConnection
+ && mShouldBlockNonMatchingNetworks == that.mShouldBlockNonMatchingNetworks
&& mNetworkId == that.mNetworkId
&& Arrays.equals(mIncludedUids, that.mIncludedUids)
&& Arrays.equals(mExcludedUids, that.mExcludedUids);
@@ -211,7 +243,8 @@
@Override
public int hashCode() {
return Objects.hash(mIsEnabled, mAllowFallbackToDefaultConnection,
- Arrays.hashCode(mIncludedUids), Arrays.hashCode(mExcludedUids), mNetworkId);
+ mShouldBlockNonMatchingNetworks, Arrays.hashCode(mIncludedUids),
+ Arrays.hashCode(mExcludedUids), mNetworkId);
}
/**
@@ -222,6 +255,7 @@
boolean mIsEnabled = false;
int mNetworkId = 0;
boolean mAllowFallbackToDefaultConnection = true;
+ boolean mShouldBlockNonMatchingNetworks = false;
int[] mIncludedUids = new int[0];
int[] mExcludedUids = new int[0];
@@ -243,10 +277,21 @@
}
/**
- * Set whether the default connection should be used as fallback.
- * This boolean configures whether the default connection (default internet or wifi)
- * should be used if a preferential network service connection is not available.
- * Default value is true
+ * Set whether fallback to the device-wide default network is allowed.
+ *
+ * This boolean configures whether the default connection (e.g. general cell network or
+ * wifi) should be used if no preferential network service connection is available. If true,
+ * the default connection will be used when no preferential service is available. If false,
+ * the UIDs subject to this configuration will have no default network.
+ * Note that while this boolean determines whether the UIDs subject to this configuration
+ * have a default network in the absence of a preferential service, apps can still
+ * explicitly decide to use another network than their default network by requesting them
+ * from the system. This boolean does not determine whether the UIDs are blocked from using
+ * such other networks.
+ * Use {@link #setShouldBlockNonMatchingNetworks(boolean)} to specify this.
+ *
+ * The default value is true.
+ *
* @param allowFallbackToDefaultConnection true if fallback is allowed else false
* @return The builder to facilitate chaining.
*/
@@ -259,6 +304,31 @@
}
/**
+ * Set whether to block UIDs from using other networks than the preferential service.
+ *
+ * Apps can inspect the list of available networks on the device and choose to use multiple
+ * of them concurrently for performance, privacy or other reasons.
+ * This boolean configures whether the concerned UIDs should be blocked from using
+ * networks that do not match the configured preferential network service even if these
+ * networks are otherwise open to all apps.
+ *
+ * The default value is false. This value can only be set to {@code true} if
+ * {@link #setFallbackToDefaultConnectionAllowed(boolean)} is set to {@code false}, because
+ * allowing fallback but blocking it does not make sense. Failure to comply with this
+ * constraint will throw when building the object.
+ *
+ * @param blockNonMatchingNetworks true if UIDs should be blocked from using non-matching
+ * networks.
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public PreferentialNetworkServiceConfig.Builder setShouldBlockNonMatchingNetworks(
+ boolean blockNonMatchingNetworks) {
+ mShouldBlockNonMatchingNetworks = blockNonMatchingNetworks;
+ return this;
+ }
+
+ /**
* Set the array of uids whose network access will go through this preferential
* network service.
* {@see #setExcludedUids(int[])}
@@ -306,8 +376,13 @@
throw new IllegalStateException("Both includedUids and excludedUids "
+ "cannot be nonempty");
}
+ if (mShouldBlockNonMatchingNetworks && mAllowFallbackToDefaultConnection) {
+ throw new IllegalStateException("A config cannot both allow fallback and "
+ + "block non-matching networks");
+ }
return new PreferentialNetworkServiceConfig(mIsEnabled,
- mAllowFallbackToDefaultConnection, mIncludedUids, mExcludedUids, mNetworkId);
+ mAllowFallbackToDefaultConnection, mShouldBlockNonMatchingNetworks,
+ mIncludedUids, mExcludedUids, mNetworkId);
}
/**
@@ -332,6 +407,7 @@
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
dest.writeBoolean(mIsEnabled);
dest.writeBoolean(mAllowFallbackToDefaultConnection);
+ dest.writeBoolean(mShouldBlockNonMatchingNetworks);
dest.writeInt(mNetworkId);
dest.writeIntArray(mIncludedUids);
dest.writeIntArray(mExcludedUids);
@@ -423,6 +499,9 @@
} else if (TAG_ALLOW_FALLBACK_TO_DEFAULT_CONNECTION.equals(tagDAM)) {
resultBuilder.setFallbackToDefaultConnectionAllowed(parser.getAttributeBoolean(
null, ATTR_VALUE, true));
+ } else if (TAG_BLOCK_NON_MATCHING_NETWORKS.equals(tagDAM)) {
+ resultBuilder.setShouldBlockNonMatchingNetworks(parser.getAttributeBoolean(
+ null, ATTR_VALUE, false));
} else if (TAG_INCLUDED_UIDS.equals(tagDAM)) {
resultBuilder.setIncludedUids(readStringListToIntArray(parser, TAG_UID));
} else if (TAG_EXCLUDED_UIDS.equals(tagDAM)) {
@@ -443,6 +522,8 @@
writeAttributeValueToXml(out, TAG_NETWORK_ID, getNetworkId());
writeAttributeValueToXml(out, TAG_ALLOW_FALLBACK_TO_DEFAULT_CONNECTION,
isFallbackToDefaultConnectionAllowed());
+ writeAttributeValueToXml(out, TAG_BLOCK_NON_MATCHING_NETWORKS,
+ shouldBlockNonMatchingNetworks());
writeAttributeValuesToXml(out, TAG_INCLUDED_UIDS, TAG_UID,
intArrayToStringList(getIncludedUids()));
writeAttributeValuesToXml(out, TAG_EXCLUDED_UIDS, TAG_UID,
@@ -460,6 +541,8 @@
pw.println(mIsEnabled);
pw.print("allowFallbackToDefaultConnection=");
pw.println(mAllowFallbackToDefaultConnection);
+ pw.print("blockNonMatchingNetworks=");
+ pw.println(mShouldBlockNonMatchingNetworks);
pw.print("includedUids=");
pw.println(mIncludedUids);
pw.print("excludedUids=");
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index c59a9dc..2e5c2df 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,5 +1,5 @@
# Assign bugs to android-enterprise-triage@google.com
-petuska@google.com
-nupursn@google.com
-shreyacsingh@google.com
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
+mdb.ae-provisioning-reviews@google.com
+petuska@google.com #{LAST_RESORT_SUGGESTION}
+nupursn@google.com #{LAST_RESORT_SUGGESTION}
+shreyacsingh@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index ddeb2f9..bad282e 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -37,6 +38,8 @@
import android.util.Log;
import android.util.Pair;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -407,33 +410,6 @@
}
/**
- * Enable/disable the framework backup scheduling entirely for the current user. When disabled,
- * no Key/Value or Full backup jobs will be scheduled by the Android framework.
- *
- * <p>Note: This does not disable backups: only their scheduling is affected and backups can
- * still be triggered manually.
- *
- * <p>Callers must hold the android.permission.BACKUP permission to use this method.
- *
- * @hide
- */
- @RequiresPermission(allOf = {android.Manifest.permission.BACKUP,
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
- public void setFrameworkSchedulingEnabled(boolean isEnabled) {
- checkServiceBinder();
- if (sService == null) {
- Log.e(TAG, "setFrameworkSchedulingEnabled() couldn't connect");
- return;
- }
-
- try {
- sService.setFrameworkSchedulingEnabledForUser(mContext.getUserId(), isEnabled);
- } catch (RemoteException e) {
- Log.e(TAG, "setFrameworkSchedulingEnabled() couldn't connect");
- }
- }
-
- /**
* Report whether the backup mechanism is currently enabled.
*
* @hide
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 041c2a7..aeb4987 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -155,22 +155,6 @@
*/
void setBackupEnabledForUser(int userId, boolean isEnabled);
-
- /**
- * Enable/disable the framework backup scheduling entirely. When disabled, no Key/Value or Full
- * backup jobs will be scheduled by the Android framework.
- *
- * <p>Note: This does not disable backups: only their scheduling is affected and backups can
- * still be triggered manually.
- *
- * <p>Callers must hold the android.permission.BACKUP permission to use this method. If
- * {@code userId} is different from the calling user id, then the caller must additionally hold
- * the android.permission.INTERACT_ACROSS_USERS_FULL permission.
- *
- * @param userId The user for which backup scheduling should be enabled/disabled.
- */
- void setFrameworkSchedulingEnabledForUser(int userId, boolean isEnabled);
-
/**
* {@link android.app.backup.IBackupManager.setBackupEnabledForUser} for the calling user id.
*/
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index c7a3b52..64dcc4d 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -82,6 +82,7 @@
final boolean mOrderedHint;
@UnsupportedAppUsage
final boolean mInitialStickyHint;
+ final boolean mAssumeDeliveredHint;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
final IBinder mToken;
@UnsupportedAppUsage
@@ -105,17 +106,38 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
boolean ordered, boolean sticky, IBinder token, int userId, int flags) {
+ this(resultCode, resultData, resultExtras, type, ordered, sticky,
+ guessAssumeDelivered(type, ordered), token, userId, flags);
+ }
+
+ /** @hide */
+ public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
+ boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
+ int userId, int flags) {
mResultCode = resultCode;
mResultData = resultData;
mResultExtras = resultExtras;
mType = type;
mOrderedHint = ordered;
mInitialStickyHint = sticky;
+ mAssumeDeliveredHint = assumeDelivered;
mToken = token;
mSendingUser = userId;
mFlags = flags;
}
+ /** @hide */
+ public static boolean guessAssumeDelivered(int type, boolean ordered) {
+ // When a caller didn't provide a concrete way of knowing if we need
+ // to report delivery, make a best-effort guess
+ if (type == TYPE_COMPONENT) {
+ return false;
+ } else if (ordered && type != TYPE_UNREGISTERED) {
+ return false;
+ }
+ return true;
+ }
+
/**
* Version of {@link BroadcastReceiver#setResultCode(int)
* BroadcastReceiver.setResultCode(int)} for
@@ -252,7 +274,7 @@
"Finishing broadcast to component " + mToken);
sendFinished(mgr);
}
- } else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
+ } else {
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing broadcast to " + mToken);
final IActivityManager mgr = ActivityManager.getService();
@@ -279,13 +301,16 @@
if (mResultExtras != null) {
mResultExtras.setAllowFds(false);
}
- if (mOrderedHint) {
- am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
- mAbortBroadcast, mFlags);
- } else {
- // This broadcast was sent to a component; it is not ordered,
- // but we still need to tell the activity manager we are done.
- am.finishReceiver(mToken, 0, null, null, false, mFlags);
+
+ // When the OS didn't assume delivery, we need to inform
+ // it that we've actually finished the delivery
+ if (!mAssumeDeliveredHint) {
+ if (mOrderedHint) {
+ am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
+ mAbortBroadcast, mFlags);
+ } else {
+ am.finishReceiver(mToken, 0, null, null, false, mFlags);
+ }
}
} catch (RemoteException ex) {
}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 2be0323..44747fa 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -171,6 +171,16 @@
public static final int FLAG_MAIN = 0x00004000;
/**
+ * Indicates that this user was created for the purposes of testing.
+ *
+ * <p>These users are subject to removal during tests and should not be used on actual devices
+ * used by humans.
+ *
+ * @hide
+ */
+ public static final int FLAG_FOR_TESTING = 0x00008000;
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -188,7 +198,8 @@
FLAG_SYSTEM,
FLAG_PROFILE,
FLAG_EPHEMERAL_ON_CREATE,
- FLAG_MAIN
+ FLAG_MAIN,
+ FLAG_FOR_TESTING
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserInfoFlag {
@@ -369,6 +380,12 @@
return (flags & FLAG_EPHEMERAL) == FLAG_EPHEMERAL;
}
+ /** @hide */
+ @TestApi
+ public boolean isForTesting() {
+ return (flags & FLAG_FOR_TESTING) == FLAG_FOR_TESTING;
+ }
+
public boolean isInitialized() {
return (flags & FLAG_INITIALIZED) == FLAG_INITIALIZED;
}
diff --git a/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java b/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
index 41f2f9c..b86b97c 100644
--- a/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
+++ b/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
@@ -17,7 +17,7 @@
package android.database.sqlite;
/**
- * Thrown if the the bind or column parameter index is out of range
+ * Thrown if the bind or column parameter index is out of range.
*/
public class SQLiteBindOrColumnIndexOutOfRangeException extends SQLiteException {
public SQLiteBindOrColumnIndexOutOfRangeException() {}
diff --git a/core/java/android/database/sqlite/SQLiteDiskIOException.java b/core/java/android/database/sqlite/SQLiteDiskIOException.java
index 01b2069..152d90a 100644
--- a/core/java/android/database/sqlite/SQLiteDiskIOException.java
+++ b/core/java/android/database/sqlite/SQLiteDiskIOException.java
@@ -17,7 +17,7 @@
package android.database.sqlite;
/**
- * An exception that indicates that an IO error occured while accessing the
+ * Indicates that an IO error occurred while accessing the
* SQLite database file.
*/
public class SQLiteDiskIOException extends SQLiteException {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index e2dedd6..da847a8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -398,6 +398,23 @@
* except that it uses {@link java.util.concurrent.Executor} as an argument
* instead of {@link android.os.Handler}.</p>
*
+ * <p>Note: If the order between some availability callbacks matters, the implementation of the
+ * executor should handle those callbacks in the same thread to maintain the callbacks' order.
+ * Some examples are:</p>
+ *
+ * <ul>
+ *
+ * <li>{@link AvailabilityCallback#onCameraAvailable} and
+ * {@link AvailabilityCallback#onCameraUnavailable} of the same camera ID.</li>
+ *
+ * <li>{@link AvailabilityCallback#onCameraAvailable} or
+ * {@link AvailabilityCallback#onCameraUnavailable} of a logical multi-camera, and {@link
+ * AvailabilityCallback#onPhysicalCameraUnavailable} or
+ * {@link AvailabilityCallback#onPhysicalCameraAvailable} of its physical
+ * cameras.</li>
+ *
+ * </ul>
+ *
* @param executor The executor which will be used to invoke the callback.
* @param callback the new callback to send camera availability notices to
*
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index d55367f..ed6a88f 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -223,11 +223,13 @@
final SomeArgs args = (SomeArgs) msg.obj;
final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+ ImeTracker.forLogging().onProgress(
+ statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
inputMethod.showSoftInputWithToken(
msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
} else {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
}
args.recycle();
return;
@@ -236,11 +238,13 @@
final SomeArgs args = (SomeArgs) msg.obj;
final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+ ImeTracker.forLogging().onProgress(
+ statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
(IBinder) args.arg1, statsToken);
} else {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
}
args.recycle();
return;
@@ -428,7 +432,7 @@
@Override
public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
int flags, ResultReceiver resultReceiver) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
flags, showInputToken, resultReceiver, statsToken));
}
@@ -437,7 +441,7 @@
@Override
public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
int flags, ResultReceiver resultReceiver) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
flags, hideInputToken, resultReceiver, statsToken));
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 872414a..ee9d8a4 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -896,7 +896,8 @@
@MainThread
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
- ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
+ ImeTracker.forLogging().onProgress(
+ mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
if (DEBUG) Log.v(TAG, "hideSoftInput()");
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
&& !mSystemCallingHideSoftInput) {
@@ -950,7 +951,8 @@
@MainThread
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
- ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
+ ImeTracker.forLogging().onProgress(
+ mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
if (DEBUG) Log.v(TAG, "showSoftInput()");
// TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
@@ -966,11 +968,11 @@
null /* icProto */);
final boolean wasVisible = isInputViewShown();
if (dispatchOnShowInputRequested(flags, false)) {
- ImeTracker.get().onProgress(mCurStatsToken,
+ ImeTracker.forLogging().onProgress(mCurStatsToken,
ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
showWindow(true);
} else {
- ImeTracker.get().onFailed(mCurStatsToken,
+ ImeTracker.forLogging().onFailed(mCurStatsToken,
ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
}
setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
@@ -2979,7 +2981,7 @@
ImeTracing.getInstance().triggerServiceDump(
"InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
null /* icProto */);
- ImeTracker.get().onProgress(mCurStatsToken,
+ ImeTracker.forLogging().onProgress(mCurStatsToken,
ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
mPrivOps.applyImeVisibilityAsync(setVisible
? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3b4e8cd..d1d3315 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -142,4 +142,8 @@
long getUserStartRealtime();
long getUserUnlockRealtime();
boolean setUserEphemeral(int userId, boolean enableEphemeral);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
+ void setBootUser(int userId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
+ int getBootUser();
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index b016c781..9db1233 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5671,6 +5671,40 @@
}
}
+ /**
+ * Sets the user who should be in the foreground when boot completes. This should be called
+ * during boot, and the provided user must be a full user (i.e. not a profile).
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public void setBootUser(@NonNull UserHandle bootUser) {
+ try {
+ mService.setBootUser(bootUser.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the user who should be in the foreground when boot completes.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ @SuppressWarnings("[AndroidFrameworkContextUserId]")
+ public @NonNull UserHandle getBootUser() {
+ try {
+ return UserHandle.of(mService.getBootUser());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
/* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3886708..9a848af 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8660,12 +8660,6 @@
public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore";
/**
- * Controls whether framework backup scheduling is enabled.
- * @hide
- */
- public static final String BACKUP_SCHEDULING_ENABLED = "backup_scheduling_enabled";
-
- /**
* Indicates whether settings backup has been fully provisioned.
* Type: int ( 0 = unprovisioned, 1 = fully provisioned )
* @hide
@@ -9408,6 +9402,14 @@
public static final int DOCK_SETUP_PROMPTED = 3;
/**
+ * Indicates that the user has started dock setup but never finished it.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_INCOMPLETE = 4;
+
+ /**
* Indicates that the user has completed dock setup.
* One of the possible states for {@link #DOCK_SETUP_STATE}.
*
@@ -9415,6 +9417,14 @@
*/
public static final int DOCK_SETUP_COMPLETED = 10;
+ /**
+ * Indicates that dock setup timed out before the user could complete it.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_TIMED_OUT = 11;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -9422,7 +9432,9 @@
DOCK_SETUP_STARTED,
DOCK_SETUP_PAUSED,
DOCK_SETUP_PROMPTED,
- DOCK_SETUP_COMPLETED
+ DOCK_SETUP_INCOMPLETE,
+ DOCK_SETUP_COMPLETED,
+ DOCK_SETUP_TIMED_OUT
})
public @interface DockSetupState {
}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 0fb9f57..b0e847c 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -166,7 +166,7 @@
}
/**
- * Description of an event that occured after the latest call to
+ * Description of an event that occurred after the latest call to
* {@link FillCallback#onSuccess(FillResponse)}.
*/
public static final class Event {
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 78f91ed..385b0aa 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -20,9 +20,11 @@
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static android.view.autofill.Helper.sDebug;
+import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.Activity;
@@ -107,6 +109,10 @@
private final @Nullable UserData mUserData;
private final @Nullable int[] mCancelIds;
private final boolean mSupportsInlineSuggestions;
+ private final @DrawableRes int mIconResourceId;
+ private final @StringRes int mServiceDisplayNameResourceId;
+ private final boolean mShowFillDialogIcon;
+ private final boolean mShowSaveDialogIcon;
private FillResponse(@NonNull Builder builder) {
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
@@ -130,6 +136,10 @@
mUserData = builder.mUserData;
mCancelIds = builder.mCancelIds;
mSupportsInlineSuggestions = builder.mSupportsInlineSuggestions;
+ mIconResourceId = builder.mIconResourceId;
+ mServiceDisplayNameResourceId = builder.mServiceDisplayNameResourceId;
+ mShowFillDialogIcon = builder.mShowFillDialogIcon;
+ mShowSaveDialogIcon = builder.mShowSaveDialogIcon;
}
/** @hide */
@@ -218,6 +228,26 @@
}
/** @hide */
+ public @DrawableRes int getIconResourceId() {
+ return mIconResourceId;
+ }
+
+ /** @hide */
+ public @StringRes int getServiceDisplayNameResourceId() {
+ return mServiceDisplayNameResourceId;
+ }
+
+ /** @hide */
+ public boolean getShowFillDialogIcon() {
+ return mShowFillDialogIcon;
+ }
+
+ /** @hide */
+ public boolean getShowSaveDialogIcon() {
+ return mShowSaveDialogIcon;
+ }
+
+ /** @hide */
@TestApi
public int getFlags() {
return mFlags;
@@ -278,6 +308,10 @@
private UserData mUserData;
private int[] mCancelIds;
private boolean mSupportsInlineSuggestions;
+ private int mIconResourceId;
+ private int mServiceDisplayNameResourceId;
+ private boolean mShowFillDialogIcon = true;
+ private boolean mShowSaveDialogIcon = true;
/**
* Triggers a custom UI before autofilling the screen with any data set in this
@@ -729,6 +763,70 @@
}
/**
+ * Overwrites Save/Fill dialog header icon with a specific one specified by resource id.
+ * The image is pulled from the package, so id should be defined in the manifest.
+ *
+ * @param id {@link android.graphics.drawable.Drawable} resource id of the icon to be used.
+ * A value of 0 indicates to use the default header icon.
+ *
+ * @return this builder
+ */
+ @NonNull
+ public Builder setIconResourceId(@DrawableRes int id) {
+ throwIfDestroyed();
+
+ mIconResourceId = id;
+ return this;
+ }
+
+ /**
+ * Overrides the service name in the Save Dialog header with a specific string defined
+ * in the service provider's manifest.xml
+ *
+ * @param id Resoure Id of the custom string defined in the provider's manifest. If set
+ * to 0, the default name will be used.
+ *
+ * @return this builder
+ */
+ @NonNull
+ public Builder setServiceDisplayNameResourceId(@StringRes int id) {
+ throwIfDestroyed();
+
+ mServiceDisplayNameResourceId = id;
+ return this;
+ }
+
+ /**
+ * Whether or not to show the Autofill provider icon inside of the Fill Dialog
+ *
+ * @param show True to show, false to hide. Defaults to true.
+ *
+ * @return this builder
+ */
+ @NonNull
+ public Builder setShowFillDialogIcon(boolean show) {
+ throwIfDestroyed();
+
+ mShowFillDialogIcon = show;
+ return this;
+ }
+
+ /**
+ * Whether or not to show the Autofill provider icon inside of the Save Dialog
+ *
+ * @param show True to show, false to hide. Defaults to true.
+ *
+ * @return this builder
+ */
+ @NonNull
+ public Builder setShowSaveDialogIcon(boolean show) {
+ throwIfDestroyed();
+
+ mShowSaveDialogIcon = show;
+ return this;
+ }
+
+ /**
* Sets a header to be shown as the first element in the list of datasets.
*
* <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset},
@@ -1024,6 +1122,10 @@
parcel.writeParcelableArray(mIgnoredIds, flags);
parcel.writeLong(mDisableDuration);
parcel.writeParcelableArray(mFieldClassificationIds, flags);
+ parcel.writeInt(mIconResourceId);
+ parcel.writeInt(mServiceDisplayNameResourceId);
+ parcel.writeBoolean(mShowFillDialogIcon);
+ parcel.writeBoolean(mShowSaveDialogIcon);
parcel.writeInt(mFlags);
parcel.writeIntArray(mCancelIds);
parcel.writeInt(mRequestId);
@@ -1089,6 +1191,11 @@
if (fieldClassifactionIds != null) {
builder.setFieldClassificationIds(fieldClassifactionIds);
}
+
+ builder.setIconResourceId(parcel.readInt());
+ builder.setServiceDisplayNameResourceId(parcel.readInt());
+ builder.setShowFillDialogIcon(parcel.readBoolean());
+ builder.setShowSaveDialogIcon(parcel.readBoolean());
builder.setFlags(parcel.readInt());
final int[] cancelIds = parcel.createIntArray();
builder.setPresentationCancelIds(cancelIds);
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
index 3a148df..b13a069 100644
--- a/core/java/android/service/smartspace/SmartspaceService.java
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -302,7 +302,7 @@
Slog.e(TAG, "Callback is null, likely the binder has died.");
return false;
}
- return mCallback.equals(callback);
+ return mCallback.asBinder().equals(callback.asBinder());
}
@Override
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index db0ede5..a70f783 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -84,7 +84,7 @@
@Nullable SharedMemory sharedMemory);
/**
- * Detect hotword from an externally supplied stream of data.
+ * Detect from an externally supplied stream of data.
*
* @return {@code true} if the request to start recognition succeeded
*/
@@ -114,7 +114,25 @@
return true;
}
- /** {@inheritDoc} */
+ /**
+ * Set configuration and pass read-only data to trusted detection service.
+ *
+ * @param options Application configuration data to provide to the
+ * {@link VisualQueryDetectionService} and {@link HotwordDetectionService}.
+ * PersistableBundle does not allow any remotable objects or other contents that can be
+ * used to communicate with other processes.
+ * @param sharedMemory The unrestricted data blob to provide to the
+ * {@link VisualQueryDetectionService} and {@link HotwordDetectionService}. Use this to
+ * provide the hotword models data or other such data to the trusted process.
+ * @throws IllegalDetectorStateException Thrown when a caller has a target SDK of
+ * Android Tiramisu or above and attempts to start a recognition when the detector is
+ * not able based on the state. Because the caller receives updates via an asynchronous
+ * callback and the state of the detector can change without caller's knowledge, a
+ * checked exception is thrown.
+ * @throws IllegalStateException if this {@link HotwordDetector} wasn't specified to use a
+ * {@link HotwordDetectionService} or {@link VisualQueryDetectionService} when it was
+ * created.
+ */
@Override
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index f3d4809..0384454 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -194,6 +194,12 @@
}
@Override
+ public void detectWithVisualSignals(
+ IDetectorSessionVisualQueryDetectionCallback callback) {
+ throw new UnsupportedOperationException("Not supported by HotwordDetectionService");
+ }
+
+ @Override
public void updateAudioFlinger(IBinder audioFlinger) {
AudioSystem.setAudioFlingerBinder(audioFlinger);
}
@@ -382,7 +388,7 @@
*/
@SystemApi
public static final class Callback {
- // TODO: need to make sure we don't store remote references, but not a high priority.
+ // TODO: consider making the constructor a test api for testing purpose
private final IDspHotwordDetectionCallback mRemoteCallback;
private Callback(IDspHotwordDetectionCallback remoteCallback) {
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 669c22b..562277e 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -35,7 +35,8 @@
import java.io.PrintWriter;
/**
- * Basic functionality for sandboxed detectors.
+ * Basic functionality for sandboxed detectors. This interface will be used by detectors that
+ * manages their service lifecycle.
*
* @hide
*/
@@ -81,9 +82,20 @@
int DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE = 2;
/**
+ * Indicates that it is a visual query detector.
+ *
+ * @hide
+ */
+ int DETECTOR_TYPE_VISUAL_QUERY_DETECTOR = 3;
+
+ /**
* Starts sandboxed detection recognition.
* <p>
- * On calling this, the system streams audio from the device microphone to this application's
+ * If a {@link VisualQueryDetector} calls this method, {@link VisualQueryDetectionService
+ * #onStartDetection(VisualQueryDetectionService.Callback)} will be called to start detection.
+ * <p>
+ * Otherwise if a {@link AlwaysOnHotwordDetector} or {@link SoftwareHotwordDetector} calls this,
+ * the system streams audio from the device microphone to this application's
* {@link HotwordDetectionService}. Audio is streamed until {@link #stopRecognition()} is
* called.
* <p>
@@ -192,6 +204,8 @@
return "trusted_hotword_dsp";
case DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
return "trusted_hotword_software";
+ case DETECTOR_TYPE_VISUAL_QUERY_DETECTOR:
+ return "visual_query_detector";
default:
return Integer.toString(detectorType);
}
@@ -244,18 +258,21 @@
void onRejected(@NonNull HotwordRejectedResult result);
/**
- * Called when the {@link HotwordDetectionService} is created by the system and given a
- * short amount of time to report its initialization state.
+ * Called when the {@link HotwordDetectionService} or {@link VisualQueryDetectionService} is
+ * created by the system and given a short amount of time to report their initialization
+ * state.
*
- * @param status Info about initialization state of {@link HotwordDetectionService}; the
- * allowed values are {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS},
+ * @param status Info about initialization state of {@link HotwordDetectionService} or
+ * {@link VisualQueryDetectionService}; allowed values are
+ * {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS},
* 1<->{@link SandboxedDetectionServiceBase#getMaxCustomInitializationStatus()},
* {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_UNKNOWN}.
*/
void onHotwordDetectionServiceInitialized(int status);
/**
- * Called with the {@link HotwordDetectionService} is restarted.
+ * Called with the {@link HotwordDetectionService} or {@link VisualQueryDetectionService} is
+ * restarted.
*
* Clients are expected to call {@link HotwordDetector#updateState} to share the state with
* the newly created service.
diff --git a/core/java/android/service/voice/IDetectorSessionVisualQueryDetectionCallback.aidl b/core/java/android/service/voice/IDetectorSessionVisualQueryDetectionCallback.aidl
new file mode 100644
index 0000000..22172ed
--- /dev/null
+++ b/core/java/android/service/voice/IDetectorSessionVisualQueryDetectionCallback.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.service.voice;
+
+/**
+ * Callback for returning the detected result from the {@link VisualQueryDetectionService}.
+ *
+ * The {@link VisualQueryDetectorSession} will overrides this interface to reach query egression
+ * control within each callback methods.
+ *
+ * @hide
+ */
+oneway interface IDetectorSessionVisualQueryDetectionCallback {
+
+ /**
+ * Called when the user attention is gained and intent to show the assistant icon in SysUI.
+ */
+ void onAttentionGained();
+
+ /**
+ * Called when the user attention is lost and intent to hide the assistant icon in SysUI.
+ */
+ void onAttentionLost();
+
+ /**
+ * Called when the detected query is streamed.
+ */
+ void onQueryDetected(in String partialQuery);
+
+ /**
+ * Called when the detected result is valid.
+ */
+ void onQueryFinished();
+
+ /**
+ * Called when the detected result is invalid.
+ */
+ void onQueryRejected();
+}
diff --git a/core/java/android/service/voice/ISandboxedDetectionService.aidl b/core/java/android/service/voice/ISandboxedDetectionService.aidl
index 5537fd1..098536d 100644
--- a/core/java/android/service/voice/ISandboxedDetectionService.aidl
+++ b/core/java/android/service/voice/ISandboxedDetectionService.aidl
@@ -24,6 +24,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.SharedMemory;
+import android.service.voice.IDetectorSessionVisualQueryDetectionCallback;
import android.service.voice.IDspHotwordDetectionCallback;
import android.view.contentcapture.IContentCaptureManager;
import android.speech.IRecognitionServiceManager;
@@ -47,6 +48,8 @@
in PersistableBundle options,
in IDspHotwordDetectionCallback callback);
+ void detectWithVisualSignals(in IDetectorSessionVisualQueryDetectionCallback callback);
+
void updateState(
in PersistableBundle options,
in SharedMemory sharedMemory,
diff --git a/core/java/android/service/voice/IVisualQueryDetectionVoiceInteractionCallback.aidl b/core/java/android/service/voice/IVisualQueryDetectionVoiceInteractionCallback.aidl
new file mode 100644
index 0000000..2eb2470
--- /dev/null
+++ b/core/java/android/service/voice/IVisualQueryDetectionVoiceInteractionCallback.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.service.voice;
+
+import android.media.AudioFormat;
+
+/**
+ * Callback for returning the detected result from the VisualQueryDetectionService.
+ *
+ * @hide
+ */
+oneway interface IVisualQueryDetectionVoiceInteractionCallback {
+
+ /**
+ * Called when the detected query is streamed
+ */
+ void onQueryDetected(in String partialQuery);
+
+ /**
+ * Called when the detected result is valid.
+ */
+ void onQueryFinished();
+
+ /**
+ * Called when the detected result is invalid.
+ */
+ void onQueryRejected();
+
+ /**
+ * Called when the detection fails due to an error.
+ */
+ void onError();
+
+}
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
index efae5c1..6a54606 100644
--- a/core/java/android/service/voice/IVoiceInteractionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -31,5 +31,5 @@
void getActiveServiceSupportedActions(in List<String> voiceActions,
in IVoiceActionCheckCallback callback);
void prepareToShowSession(in Bundle args, int flags);
- void showSessionFailed();
+ void showSessionFailed(in Bundle args);
}
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index 7926901..fde0afb 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.view.contentcapture.IContentCaptureManager;
+import java.util.Objects;
import java.util.function.IntConsumer;
/**
@@ -80,6 +81,19 @@
private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
@Override
+ public void detectWithVisualSignals(
+ IDetectorSessionVisualQueryDetectionCallback callback) {
+ Log.v(TAG, "#detectWithVisualSignals");
+ VisualQueryDetectionService.this.onStartDetection(new Callback(callback));
+ }
+
+ @Override
+ public void stopDetection() {
+ Log.v(TAG, "#stopDetection");
+ VisualQueryDetectionService.this.onStopDetection();
+ }
+
+ @Override
public void updateState(PersistableBundle options, SharedMemory sharedMemory,
IRemoteCallback callback) throws RemoteException {
Log.v(TAG, "#updateState" + (callback != null ? " with callback" : ""));
@@ -128,11 +142,6 @@
public void updateRecognitionServiceManager(IRecognitionServiceManager manager) {
Log.v(TAG, "Ignore #updateRecognitionServiceManager");
}
-
- @Override
- public void stopDetection() {
- throw new UnsupportedOperationException("Not supported by VisualQueryDetectionService");
- }
};
/**
@@ -216,18 +225,37 @@
*/
public static final class Callback {
+ // TODO: consider making the constructor a test api for testing purpose
+ public Callback() {
+ mRemoteCallback = null;
+ }
+
+ private final IDetectorSessionVisualQueryDetectionCallback mRemoteCallback;
+
+ private Callback(IDetectorSessionVisualQueryDetectionCallback remoteCallback) {
+ mRemoteCallback = remoteCallback;
+ }
+
/**
* Informs attention listener that the user attention is gained.
*/
public void onAttentionGained() {
- //TODO(b/265345361): call internal callbacks to send signal to the interactor
+ try {
+ mRemoteCallback.onAttentionGained();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* Informs attention listener that the user attention is lost.
*/
public void onAttentionLost() {
- //TODO(b/265345361): call internal callbacks to send signal to the interactor
+ try {
+ mRemoteCallback.onAttentionLost();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -241,8 +269,13 @@
* @throws IllegalStateException if method called without attention gained.
*/
public void onQueryDetected(@NonNull String partialQuery) throws IllegalStateException {
- //TODO(b/265345361): call internal callbacks to send signal to the interactor
- //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+ Objects.requireNonNull(partialQuery);
+ try {
+ mRemoteCallback.onQueryDetected(partialQuery);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("#onQueryDetected must be only be triggered after "
+ + "calling #onAttentionGained to be in the attention gained state.");
+ }
}
/**
@@ -254,8 +287,12 @@
* @throws IllegalStateException if method called without query streamed.
*/
public void onQueryRejected() throws IllegalStateException {
- //TODO(b/265345361): call internal callbacks to send signal to the interactor
- //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+ try {
+ mRemoteCallback.onQueryRejected();
+ } catch (RemoteException e) {
+ throw new IllegalStateException("#onQueryRejected must be only be triggered after "
+ + "calling #onQueryDetected to be in the query streaming state.");
+ }
}
/**
@@ -267,8 +304,12 @@
* @throws IllegalStateException if method called without query streamed.
*/
public void onQueryFinished() throws IllegalStateException {
- //TODO(b/265345361): call internal callbacks to send signal to the interactor
- //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+ try {
+ mRemoteCallback.onQueryFinished();
+ } catch (RemoteException e) {
+ throw new IllegalStateException("#onQueryFinished must be only be triggered after "
+ + "calling #onQueryDetected to be in the query streaming state.");
+ }
}
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
new file mode 100644
index 0000000..6917576
--- /dev/null
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2023 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.service.voice;
+
+import static android.Manifest.permission.CAMERA;
+import static android.Manifest.permission.RECORD_AUDIO;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.AudioFormat;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.util.Slog;
+
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVoiceInteractionManagerService;
+
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Manages VisualQueryDetectionService.
+ *
+ * This detector provides necessary functionalities to initialize, start, update and destroy a
+ * {@link VisualQueryDetectionService}.
+ *
+ * @hide
+ **/
+@SystemApi
+@SuppressLint("NotCloseable")
+public class VisualQueryDetector {
+ private static final String TAG = VisualQueryDetector.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private final Callback mCallback;
+ private final Executor mExecutor;
+ private final IVoiceInteractionManagerService mManagerService;
+ private final VisualQueryDetectorInitializationDelegate mInitializationDelegate;
+
+ VisualQueryDetector(
+ IVoiceInteractionManagerService managerService,
+ @NonNull @CallbackExecutor Executor executor,
+ Callback callback) {
+ mManagerService = managerService;
+ mCallback = callback;
+ mExecutor = executor;
+ mInitializationDelegate = new VisualQueryDetectorInitializationDelegate();
+ }
+
+ /**
+ * Initialize the {@link VisualQueryDetectionService} by passing configurations and read-only
+ * data.
+ */
+ void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
+ mInitializationDelegate.initialize(options, sharedMemory);
+ }
+
+ /**
+ * Set configuration and pass read-only data to {@link VisualQueryDetectionService}.
+ *
+ * @see HotwordDetector#updateState(PersistableBundle, SharedMemory)
+ */
+ public void updateState(@Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory) throws
+ HotwordDetector.IllegalDetectorStateException {
+ mInitializationDelegate.updateState(options, sharedMemory);
+ }
+
+
+ /**
+ * On calling this method, {@link VisualQueryDetectionService
+ * #onStartDetection(VisualQueryDetectionService.Callback)} will be called to start using
+ * visual signals such as camera frames and microphone audio to perform detection. When user
+ * attention is captured and the {@link VisualQueryDetectionService} streams queries,
+ * {@link VisualQueryDetector.Callback#onQueryDetected(String)} is called to control the
+ * behavior of handling {@code transcribedText}. When the query streaming is finished,
+ * {@link VisualQueryDetector.Callback#onQueryFinished()} is called. If the current streamed
+ * query is invalid, {@link VisualQueryDetector.Callback#onQueryRejected()} is called to abandon
+ * the streamed query.
+ *
+ * @see HotwordDetector#startRecognition()
+ */
+ @RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
+ public boolean startRecognition() throws HotwordDetector.IllegalDetectorStateException {
+ if (DEBUG) {
+ Slog.i(TAG, "#startRecognition");
+ }
+ // check if the detector is active with the initialization delegate
+ mInitializationDelegate.startRecognition();
+
+ try {
+ mManagerService.startPerceiving(new BinderCallback(mExecutor, mCallback));
+ } catch (SecurityException e) {
+ Slog.e(TAG, "startRecognition failed: " + e);
+ return false;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return true;
+ }
+
+ /**
+ * Stops visual query detection recognition.
+ *
+ * @see HotwordDetector#stopRecognition()
+ */
+ @RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
+ public boolean stopRecognition() throws HotwordDetector.IllegalDetectorStateException {
+ if (DEBUG) {
+ Slog.i(TAG, "#stopRecognition");
+ }
+ // check if the detector is active with the initialization delegate
+ mInitializationDelegate.startRecognition();
+
+ try {
+ mManagerService.stopPerceiving();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return true;
+ }
+
+ /**
+ * Destroy the current detector.
+ *
+ * @see HotwordDetector#destroy()
+ */
+ public void destroy() {
+ if (DEBUG) {
+ Slog.i(TAG, "#destroy");
+ }
+ mInitializationDelegate.destroy();
+ }
+
+ /** @hide */
+ public void dump(String prefix, PrintWriter pw) {
+ // TODO: implement this
+ }
+
+ /** @hide */
+ public HotwordDetector getInitializationDelegate() {
+ return mInitializationDelegate;
+ }
+
+ /** @hide */
+ void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
+ mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+ }
+
+ /**
+ * A class that lets a VoiceInteractionService implementation interact with
+ * visual query detection APIs.
+ */
+ public interface Callback {
+
+ /**
+ * Called when the {@link VisualQueryDetectionService} starts to stream partial queries.
+ *
+ * @param partialQuery The partial query in a text form being streamed.
+ */
+ void onQueryDetected(@NonNull String partialQuery);
+
+ /**
+ * Called when the {@link VisualQueryDetectionService} decides to abandon the streamed
+ * partial queries.
+ */
+ void onQueryRejected();
+
+ /**
+ * Called when the {@link VisualQueryDetectionService} finishes streaming partial queries.
+ */
+ void onQueryFinished();
+
+ /**
+ * Called when the {@link VisualQueryDetectionService} is created by the system and given a
+ * short amount of time to report its initialization state.
+ *
+ * @param status Info about initialization state of {@link VisualQueryDetectionService}; the
+ * allowed values are {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS},
+ * 1<->{@link SandboxedDetectionServiceBase#getMaxCustomInitializationStatus()},
+ * {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_UNKNOWN}.
+ */
+ void onVisualQueryDetectionServiceInitialized(int status);
+
+ /**
+ * Called with the {@link VisualQueryDetectionService} is restarted.
+ *
+ * Clients are expected to call {@link HotwordDetector#updateState} to share the state with
+ * the newly created service.
+ */
+ void onVisualQueryDetectionServiceRestarted();
+
+ /**
+ * Called when the detection fails due to an error.
+ */
+ //TODO(b/265390855): Replace this callback with the new onError(DetectorError) design.
+ void onError();
+ }
+
+ private class VisualQueryDetectorInitializationDelegate extends AbstractDetector {
+
+ VisualQueryDetectorInitializationDelegate() {
+ super(mManagerService, null);
+ }
+
+ @Override
+ void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
+ initAndVerifyDetector(options, sharedMemory,
+ new InitializationStateListener(mExecutor, mCallback),
+ DETECTOR_TYPE_VISUAL_QUERY_DETECTOR);
+ }
+
+ @Override
+ public boolean stopRecognition() throws IllegalDetectorStateException {
+ throwIfDetectorIsNoLongerActive();
+ return true;
+ }
+
+ @Override
+ public boolean startRecognition() throws IllegalDetectorStateException {
+ throwIfDetectorIsNoLongerActive();
+ return true;
+ }
+
+ @Override
+ public final boolean startRecognition(
+ @NonNull ParcelFileDescriptor audioStream,
+ @NonNull AudioFormat audioFormat,
+ @Nullable PersistableBundle options) throws IllegalDetectorStateException {
+ //No-op, not supported by VisualQueryDetector as it should be trusted.
+ return false;
+ }
+
+ @Override
+ public boolean isUsingSandboxedDetectionService() {
+ return true;
+ }
+ }
+
+ private static class BinderCallback
+ extends IVisualQueryDetectionVoiceInteractionCallback.Stub {
+ private final Executor mExecutor;
+ private final VisualQueryDetector.Callback mCallback;
+
+ BinderCallback(Executor executor, VisualQueryDetector.Callback callback) {
+ this.mExecutor = executor;
+ this.mCallback = callback;
+ }
+
+ /** Called when the detected result is valid. */
+ @Override
+ public void onQueryDetected(@NonNull String partialQuery) {
+ Slog.v(TAG, "BinderCallback#onQueryDetected");
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> mCallback.onQueryDetected(partialQuery)));
+ }
+
+ @Override
+ public void onQueryFinished() {
+ Slog.v(TAG, "BinderCallback#onQueryFinished");
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> mCallback.onQueryFinished()));
+ }
+
+ @Override
+ public void onQueryRejected() {
+ Slog.v(TAG, "BinderCallback#onQueryRejected");
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> mCallback.onQueryRejected()));
+ }
+
+ /** Called when the detection fails due to an error. */
+ @Override
+ public void onError() {
+ Slog.v(TAG, "BinderCallback#onError");
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> mCallback.onError()));
+ }
+
+ }
+
+
+ private static class InitializationStateListener
+ extends IHotwordRecognitionStatusCallback.Stub {
+ private final Executor mExecutor;
+ private final Callback mCallback;
+
+ InitializationStateListener(Executor executor, Callback callback) {
+ this.mExecutor = executor;
+ this.mCallback = callback;
+ }
+
+ @Override
+ public void onKeyphraseDetected(
+ SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
+ HotwordDetectedResult result) {
+ if (DEBUG) {
+ Slog.i(TAG, "Ignored #onKeyphraseDetected event");
+ }
+ }
+
+ @Override
+ public void onGenericSoundTriggerDetected(
+ SoundTrigger.GenericRecognitionEvent recognitionEvent) throws RemoteException {
+ if (DEBUG) {
+ Slog.i(TAG, "Ignored #onGenericSoundTriggerDetected event");
+ }
+ }
+
+ @Override
+ public void onRejected(HotwordRejectedResult result) throws RemoteException {
+ if (DEBUG) {
+ Slog.i(TAG, "Ignored #onRejected event");
+ }
+ }
+
+ @Override
+ public void onRecognitionPaused() throws RemoteException {
+ if (DEBUG) {
+ Slog.i(TAG, "Ignored #onRecognitionPaused event");
+ }
+ }
+
+ @Override
+ public void onRecognitionResumed() throws RemoteException {
+ if (DEBUG) {
+ Slog.i(TAG, "Ignored #onRecognitionResumed event");
+ }
+ }
+
+ @Override
+ public void onStatusReported(int status) {
+ Slog.v(TAG, "onStatusReported" + (DEBUG ? "(" + status + ")" : ""));
+ //TODO: rename the target callback with a more general term
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> mCallback.onVisualQueryDetectionServiceInitialized(status)));
+
+ }
+
+ @Override
+ public void onProcessRestarted() throws RemoteException {
+ Slog.v(TAG, "onProcessRestarted()");
+ //TODO: rename the target callback with a more general term
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> mCallback.onVisualQueryDetectionServiceRestarted()));
+ }
+
+ @Override
+ public void onError(int status) throws RemoteException {
+ Slog.v(TAG, "Initialization Error: (" + status + ")");
+ // Do nothing
+ }
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index d49b5a5..df739e3 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -17,6 +17,7 @@
package android.service.voice;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -58,7 +59,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
-
+import java.util.concurrent.Executor;
/**
* Top-level service of the current global voice interactor, which is providing
* support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
@@ -94,6 +95,20 @@
public static final String SERVICE_META_DATA = "android.voice_interaction";
/**
+ * Bundle key used to specify the id when the system prepares to show session. It increases for
+ * each request.
+ * <p>
+ * Type: int
+ * </p>
+ * @see #showSession(Bundle, int)
+ * @see #onPrepareToShowSession(Bundle, int)
+ * @see #onShowSessionFailed(Bundle)
+ * @see VoiceInteractionSession#onShow(Bundle, int)
+ * @see VoiceInteractionSession#show(Bundle, int)
+ */
+ public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
+
+ /**
* For apps targeting Build.VERSION_CODES.TRAMISU and above, implementors of this
* service can create multiple AlwaysOnHotwordDetector instances in parallel. They will
* also e ale to create a single SoftwareHotwordDetector in parallel with any other
@@ -169,15 +184,17 @@
}
@Override
- public void showSessionFailed() {
+ public void showSessionFailed(@NonNull Bundle args) {
Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
VoiceInteractionService::onShowSessionFailed,
- VoiceInteractionService.this));
+ VoiceInteractionService.this, args));
}
};
IVoiceInteractionManagerService mSystemService;
+ private VisualQueryDetector mActiveVisualQueryDetector;
+
private final Object mLock = new Object();
private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
@@ -202,9 +219,10 @@
* bind the session service.
*
* @param args The arguments that were supplied to {@link #showSession(Bundle, int)}.
+ * It always includes {@link #KEY_SHOW_SESSION_ID}.
* @param flags The show flags originally provided to {@link #showSession(Bundle, int)}.
* @see #showSession(Bundle, int)
- * @see #onShowSessionFailed()
+ * @see #onShowSessionFailed(Bundle)
* @see VoiceInteractionSession#onShow(Bundle, int)
* @see VoiceInteractionSession#show(Bundle, int)
*/
@@ -214,12 +232,14 @@
/**
* Called when the show session failed. E.g. When the system bound the session service failed.
*
+ * @param args Additional info about the show session attempt that failed. For now, includes
+ * {@link #KEY_SHOW_SESSION_ID}.
* @see #showSession(Bundle, int)
* @see #onPrepareToShowSession(Bundle, int)
* @see VoiceInteractionSession#onShow(Bundle, int)
* @see VoiceInteractionSession#show(Bundle, int)
*/
- public void onShowSessionFailed() {
+ public void onShowSessionFailed(@NonNull Bundle args) {
}
/**
@@ -583,6 +603,85 @@
}
/**
+ * Creates a {@link VisualQueryDetector} and initializes the application's
+ * {@link VisualQueryDetectionService} using {@code options} and {@code sharedMemory}.
+ *
+ * <p>To be able to call this, you need to set android:visualQueryDetectionService in the
+ * android.voice_interaction metadata file to a valid visual query detection service, and set
+ * android:isolatedProcess="true" in the service's declaration. Otherwise, this throws an
+ * {@link IllegalStateException}.
+ *
+ * <p>Using this has a noticeable impact on battery, since the microphone is kept open
+ * for the lifetime of the recognition {@link VisualQueryDetector#startRecognition() session}.
+ *
+ * @param options Application configuration data to be provided to the
+ * {@link VisualQueryDetectionService}. PersistableBundle does not allow any remotable objects
+ * or other contents that can be used to communicate with other processes.
+ * @param sharedMemory The unrestricted data blob to be provided to the
+ * {@link VisualQueryDetectionService}. Use this to provide models or other such data to the
+ * sandboxed process.
+ * @param callback The callback to notify of detection events.
+ * @return An instanece of {@link VisualQueryDetector}.
+ * @throws UnsupportedOperationException if only single detector is supported. Multiple detector
+ * is only available for apps targeting {@link Build.VERSION_CODES#TIRAMISU} and above.
+ * @throws IllegalStateException when there is an existing {@link VisualQueryDetector}, or when
+ * there is a non-trusted hotword detector running.
+ *
+ * @hide
+ */
+ // TODO: add MANAGE_HOTWORD_DETECTION permission to protect this API and update java doc.
+ @SystemApi
+ @NonNull
+ public final VisualQueryDetector createVisualQueryDetector(
+ @Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull VisualQueryDetector.Callback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ if (mSystemService == null) {
+ throw new IllegalStateException("Not available until onReady() is called");
+ }
+ synchronized (mLock) {
+ if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
+ throw new UnsupportedOperationException("VisualQueryDetector is only available if "
+ + "multiple detectors are allowed");
+ } else {
+ if (mActiveVisualQueryDetector != null) {
+ throw new IllegalStateException(
+ "There is already an active VisualQueryDetector. "
+ + "It must be destroyed to create a new one.");
+ }
+ for (HotwordDetector detector : mActiveDetectors) {
+ if (!detector.isUsingSandboxedDetectionService()) {
+ throw new IllegalStateException(
+ "It disallows to create trusted and non-trusted detectors "
+ + "at the same time.");
+ }
+ }
+ }
+
+ VisualQueryDetector visualQueryDetector =
+ new VisualQueryDetector(mSystemService, executor, callback);
+ HotwordDetector visualQueryDetectorInitializationDelegate =
+ visualQueryDetector.getInitializationDelegate();
+ mActiveDetectors.add(visualQueryDetectorInitializationDelegate);
+
+ try {
+ visualQueryDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+ visualQueryDetector.initialize(options, sharedMemory);
+ } catch (Exception e) {
+ mActiveDetectors.remove(visualQueryDetectorInitializationDelegate);
+ visualQueryDetector.destroy();
+ throw e;
+ }
+ mActiveVisualQueryDetector = visualQueryDetector;
+ return visualQueryDetector;
+ }
+ }
+
+ /**
* Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the
* pre-bundled system voice models.
* @hide
@@ -637,6 +736,10 @@
private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) {
synchronized (mLock) {
+ if (mActiveVisualQueryDetector != null
+ && detector == mActiveVisualQueryDetector.getInitializationDelegate()) {
+ mActiveVisualQueryDetector = null;
+ }
mActiveDetectors.remove(detector);
shutdownHotwordDetectionServiceIfRequiredLocked();
}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 2c70229..d55fede 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1763,7 +1763,8 @@
* @param args The arguments that were supplied to
* {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
* Some example keys include : "invocation_type", "invocation_phone_state",
- * "invocation_time_ms", Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
+ * {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, "invocation_time_ms",
+ * Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
* in milliseconds of the KeyEvent that triggered Assistant and
* Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
* referring to the device that sent the request.
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 7f45b38..196bac2 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -24,6 +24,7 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
+import android.text.method.OffsetMapping;
import android.text.style.ReplacementSpan;
import android.text.style.UpdateLayout;
import android.text.style.WrapTogetherSpan;
@@ -1095,10 +1096,27 @@
}
public void beforeTextChanged(CharSequence s, int where, int before, int after) {
- // Intentionally empty
+ final DynamicLayout dynamicLayout = mLayout.get();
+ if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+ final OffsetMapping transformedText = (OffsetMapping) dynamicLayout.mDisplay;
+ if (mTransformedTextUpdate == null) {
+ mTransformedTextUpdate = new OffsetMapping.TextUpdate(where, before, after);
+ } else {
+ mTransformedTextUpdate.where = where;
+ mTransformedTextUpdate.before = before;
+ mTransformedTextUpdate.after = after;
+ }
+ transformedText.originalToTransformed(mTransformedTextUpdate);
+ }
}
public void onTextChanged(CharSequence s, int where, int before, int after) {
+ final DynamicLayout dynamicLayout = mLayout.get();
+ if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+ where = mTransformedTextUpdate.where;
+ before = mTransformedTextUpdate.before;
+ after = mTransformedTextUpdate.after;
+ }
reflow(s, where, before, after);
}
@@ -1106,14 +1124,34 @@
// Intentionally empty
}
+ /**
+ * Reflow the {@link DynamicLayout} at the given range from {@code start} to the
+ * {@code end}.
+ * If the display text in this {@link DynamicLayout} is a {@link OffsetMapping} instance
+ * (which means it's also a transformed text), it will transform the given range first and
+ * then reflow.
+ */
+ private void transformAndReflow(Spannable s, int start, int end) {
+ final DynamicLayout dynamicLayout = mLayout.get();
+ if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+ final OffsetMapping transformedText = (OffsetMapping) dynamicLayout.mDisplay;
+ start = transformedText.originalToTransformed(start,
+ OffsetMapping.MAP_STRATEGY_CHARACTER);
+ end = transformedText.originalToTransformed(end,
+ OffsetMapping.MAP_STRATEGY_CHARACTER);
+ }
+ reflow(s, start, end - start, end - start);
+ }
+
public void onSpanAdded(Spannable s, Object o, int start, int end) {
- if (o instanceof UpdateLayout)
- reflow(s, start, end - start, end - start);
+ if (o instanceof UpdateLayout) {
+ transformAndReflow(s, start, end);
+ }
}
public void onSpanRemoved(Spannable s, Object o, int start, int end) {
if (o instanceof UpdateLayout)
- reflow(s, start, end - start, end - start);
+ transformAndReflow(s, start, end);
}
public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
@@ -1123,12 +1161,13 @@
// instead of causing an exception
start = 0;
}
- reflow(s, start, end - start, end - start);
- reflow(s, nstart, nend - nstart, nend - nstart);
+ transformAndReflow(s, start, end);
+ transformAndReflow(s, nstart, nend);
}
}
private WeakReference<DynamicLayout> mLayout;
+ private OffsetMapping.TextUpdate mTransformedTextUpdate;
}
@Override
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index d3367d0..37474e5 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -68,6 +68,9 @@
@Override
protected boolean left(TextView widget, Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
if (isSelecting(buffer)) {
return Selection.extendLeft(buffer, layout);
@@ -78,6 +81,9 @@
@Override
protected boolean right(TextView widget, Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
if (isSelecting(buffer)) {
return Selection.extendRight(buffer, layout);
@@ -88,6 +94,9 @@
@Override
protected boolean up(TextView widget, Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
if (isSelecting(buffer)) {
return Selection.extendUp(buffer, layout);
@@ -98,6 +107,9 @@
@Override
protected boolean down(TextView widget, Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
if (isSelecting(buffer)) {
return Selection.extendDown(buffer, layout);
@@ -108,6 +120,9 @@
@Override
protected boolean pageUp(TextView widget, Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
final boolean selecting = isSelecting(buffer);
final int targetY = getCurrentLineTop(buffer, layout) - getPageHeight(widget);
@@ -132,6 +147,9 @@
@Override
protected boolean pageDown(TextView widget, Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
final boolean selecting = isSelecting(buffer);
final int targetY = getCurrentLineTop(buffer, layout) + getPageHeight(widget);
@@ -176,6 +194,9 @@
@Override
protected boolean lineStart(TextView widget, Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
if (isSelecting(buffer)) {
return Selection.extendToLeftEdge(buffer, layout);
@@ -186,6 +207,9 @@
@Override
protected boolean lineEnd(TextView widget, Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
if (isSelecting(buffer)) {
return Selection.extendToRightEdge(buffer, layout);
@@ -224,6 +248,9 @@
@Override
public boolean previousParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
if (isSelecting(buffer)) {
return Selection.extendToParagraphStart(buffer);
@@ -234,6 +261,9 @@
@Override
public boolean nextParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
+ if (widget.isOffsetMappingAvailable()) {
+ return false;
+ }
final Layout layout = widget.getLayout();
if (isSelecting(buffer)) {
return Selection.extendToParagraphEnd(buffer);
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 01989d5..9a120d5 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -440,8 +440,9 @@
private boolean deleteLine(View view, Editable content) {
if (view instanceof TextView) {
- final Layout layout = ((TextView) view).getLayout();
- if (layout != null) {
+ final TextView textView = (TextView) view;
+ final Layout layout = textView.getLayout();
+ if (layout != null && !textView.isOffsetMappingAvailable()) {
final int line = layout.getLineForOffset(Selection.getSelectionStart(content));
final int start = layout.getLineStart(line);
final int end = layout.getLineEnd(line);
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index c544c41..dae978e 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -100,6 +100,10 @@
private boolean action(int what, TextView widget, Spannable buffer) {
Layout layout = widget.getLayout();
+ if (widget.isOffsetMappingAvailable()) {
+ // The text in the layout is transformed and has OffsetMapping, don't do anything.
+ return false;
+ }
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
diff --git a/core/java/android/text/method/OffsetMapping.java b/core/java/android/text/method/OffsetMapping.java
new file mode 100644
index 0000000..fcf3de6
--- /dev/null
+++ b/core/java/android/text/method/OffsetMapping.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2022 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.text.method;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The interface for the index mapping information of a transformed text returned by
+ * {@link TransformationMethod}. This class is mainly used to support the
+ * {@link TransformationMethod} that alters the text length.
+ * @hide
+ */
+public interface OffsetMapping {
+ /**
+ * The mapping strategy for a character offset.
+ *
+ * @see #originalToTransformed(int, int)
+ * @see #transformedToOriginal(int, int)
+ */
+ int MAP_STRATEGY_CHARACTER = 0;
+
+ /**
+ * The mapping strategy for a cursor position.
+ *
+ * @see #originalToTransformed(int, int)
+ * @see #transformedToOriginal(int, int)
+ */
+ int MAP_STRATEGY_CURSOR = 1;
+
+ @IntDef(prefix = { "MAP_STRATEGY" }, value = {
+ MAP_STRATEGY_CHARACTER,
+ MAP_STRATEGY_CURSOR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MapStrategy {}
+
+ /**
+ * Map an offset at original text to the offset at transformed text. <br/>
+ *
+ * This function must be a monotonically non-decreasing function. In other words, if the offset
+ * advances at the original text, the offset at the transformed text must advance or stay there.
+ * <br/>
+ *
+ * Depending on the mapping strategy, a same offset can be mapped differently. For example,
+ * <pre>
+ * Original: ABCDE
+ * Transformed: ABCXDE ('X' is introduced due to the transformation.)
+ * </pre>
+ * Let's check the offset 3, which is the offset of the character 'D'.
+ * If we want to map the character offset 3, it should be mapped to index 4.
+ * If we want to map the cursor offset 3 (the offset of the character before which the cursor is
+ * placed), it's unclear if the mapped cursor is before 'X' or after 'X'.
+ * This depends on how the transformed text reacts an insertion at offset 3 in the original
+ * text. Assume character 'V' is insert at offset 3 in the original text, and the original text
+ * become "ABCVDE". The transformed text can be:
+ * <pre>
+ * 1) "ABCVXDE"
+ * or
+ * 2) "ABCXVDE"
+ * </pre>
+ * In the first case, the offset 3 should be mapped to 3 (before 'X'). And in the second case,
+ * the offset should be mapped to 4 (after 'X').<br/>
+ *
+ * In some cases, a character offset at the original text doesn't have a proper corresponding
+ * offset at the transformed text. For example:
+ * <pre>
+ * Original: ABCDE
+ * Transformed: ABDE ('C' is deleted due to the transformation.)
+ * </pre>
+ * The character offset 2 can be mapped either to offset 2 or 3, but neither is meaningful. For
+ * convenience, it MUST map to the next offset (offset 3 in this case), or the
+ * transformedText.length() if there is no valid character to map.
+ * This is mandatory when the map strategy is {@link #MAP_STRATEGY_CHARACTER}, but doesn't
+ * apply for other map strategies.
+ *
+ * @param offset the offset at the original text. It's normally equal to or less than the
+ * originalText.length(). When {@link #MAP_STRATEGY_CHARACTER} is passed, it must
+ * be less than originalText.length(). For convenience, it's also allowed to be
+ * negative, which represents an invalid offset. When the given offset is
+ * negative, this method should return it as it is.
+ * @param strategy the mapping strategy. Depending on its value, the same offset can be mapped
+ * differently.
+ * @return the mapped offset at the transformed text, must be equal to or less than the
+ * transformedText.length().
+ *
+ * @see #transformedToOriginal(int, int)
+ */
+ int originalToTransformed(int offset, @MapStrategy int strategy);
+
+ /**
+ * Map an offset at transformed text to the offset at original text. This is the reverse method
+ * of {@link #originalToTransformed(int, int)}. <br/>
+ * This function must be a monotonically non-decreasing function. In other words, if the offset
+ * advances at the original text, the offset at the transformed text must advance or stay there.
+ * <br/>
+ * Similar to the {@link #originalToTransformed(int, int)} if a character offset doesn't have a
+ * corresponding offset at the transformed text, it MUST return the value as the previous
+ * offset. This is mandatory when the map strategy is {@link #MAP_STRATEGY_CHARACTER},
+ * but doesn't apply for other map strategies.
+ *
+ * @param offset the offset at the transformed text. It's normally equal to or less than the
+ * transformedText.length(). When {@link #MAP_STRATEGY_CHARACTER} is passed, it
+ * must be less than transformedText.length(). For convenience, it's also allowed
+ * to be negative, which represents an invalid offset. When the given offset is
+ * negative, this method should return it as it is.
+ * @param strategy the mapping strategy. Depending on its value, the same offset can be mapped
+ * differently.
+ * @return the mapped offset at the original text, must be equal to or less than the
+ * original.length().
+ *
+ * @see #originalToTransformed(int, int)
+ */
+ int transformedToOriginal(int offset, @MapStrategy int strategy);
+
+ /**
+ * Map a text update in the original text to an update the transformed text.
+ * This method used to determine how the transformed text is updated in response to an
+ * update in the original text. It is always called before the original text being changed.
+ *
+ * The main usage of this method is to update text layout incrementally. So it should report
+ * the range where text needs to be laid out again.
+ *
+ * @param textUpdate the {@link TextUpdate} object containing the text update information on
+ * the original text. The transformed text update information will also be
+ * stored at this object.
+ */
+ void originalToTransformed(TextUpdate textUpdate);
+
+ /**
+ * The class that stores the text update information that from index <code>where</code>,
+ * <code>after</code> characters will replace the old text that has length <code>before</code>.
+ */
+ class TextUpdate {
+ /** The start index of the text update range, inclusive */
+ public int where;
+ /** The length of the replaced old text. */
+ public int before;
+ /** The length of the new text that replaces the old text. */
+ public int after;
+
+ /**
+ * Creates a {@link TextUpdate} object.
+ * @param where the start index of the text update range.
+ * @param before the length of the replaced old text.
+ * @param after the length of the new text that replaces the old text.
+ */
+ public TextUpdate(int where, int before, int after) {
+ this.where = where;
+ this.before = before;
+ this.after = after;
+ }
+ }
+}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index e89be47..3e84153 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -123,7 +123,7 @@
// TODO: ResultReceiver for IME.
// TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
if (getControl() == null) {
@@ -164,12 +164,13 @@
// - we do already have one, but we have control and use the passed in token
// for the insets animation already.
if (statsToken == null || getControl() != null) {
- statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
+ statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
+ Process.myUid(),
ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8e8e28a..c992450 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -26,6 +26,7 @@
import static android.view.WindowInsets.Type.LAST;
import static android.view.WindowInsets.Type.all;
import static android.view.WindowInsets.Type.ime;
+import static android.view.inputmethod.ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL;
import android.animation.AnimationHandler;
import android.animation.Animator;
@@ -35,6 +36,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -60,6 +63,7 @@
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.ImeTracker.InputMethodJankContext;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -186,6 +190,14 @@
@Nullable
String getRootViewTitle();
+ /**
+ * @return the context related to the rootView.
+ */
+ @Nullable
+ default Context getRootViewContext() {
+ return null;
+ }
+
/** @see ViewRootImpl#dipToPx */
int dipToPx(int dips);
@@ -306,7 +318,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
ANIMATION_TYPE_USER, ANIMATION_TYPE_RESIZE})
- @interface AnimationType {
+ public @interface AnimationType {
}
/**
@@ -321,6 +333,27 @@
/** Logging listener. */
private WindowInsetsAnimationControlListener mLoggingListener;
+ /** Context for {@link android.view.inputmethod.ImeTracker.ImeJankTracker} to monitor jank. */
+ private final InputMethodJankContext mJankContext = new InputMethodJankContext() {
+ @Override
+ public Context getDisplayContext() {
+ return mHost != null ? mHost.getRootViewContext() : null;
+ }
+
+ @Override
+ public SurfaceControl getTargetSurfaceControl() {
+ final InsetsSourceConsumer imeSourceConsumer = mSourceConsumers.get(ITYPE_IME);
+ final InsetsSourceControl imeSourceControl =
+ imeSourceConsumer != null ? imeSourceConsumer.getControl() : null;
+ return imeSourceControl != null ? imeSourceControl.getLeash() : null;
+ }
+
+ @Override
+ public String getHostPackageName() {
+ return mHost != null ? mHost.getRootViewContext().getPackageName() : null;
+ }
+ };
+
/**
* The default implementation of listener, to be used by InsetsController and InsetsPolicy to
* animate insets.
@@ -338,6 +371,7 @@
private final boolean mDisable;
private final int mFloatingImeBottomInset;
private final WindowInsetsAnimationControlListener mLoggingListener;
+ private final InputMethodJankContext mInputMethodJankContext;
private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
new ThreadLocal<AnimationHandler>() {
@@ -351,7 +385,8 @@
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
@InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
- int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) {
+ int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener,
+ @Nullable InputMethodJankContext jankContext) {
mShow = show;
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
@@ -360,6 +395,7 @@
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
mLoggingListener = loggingListener;
+ mInputMethodJankContext = jankContext;
}
@Override
@@ -406,10 +442,26 @@
+ insetsFraction);
});
mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mInputMethodJankContext == null) return;
+ ImeTracker.forJank().onRequestAnimation(
+ mInputMethodJankContext,
+ mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+ !mHasAnimationCallbacks);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (mInputMethodJankContext == null) return;
+ ImeTracker.forJank().onCancelAnimation();
+ }
@Override
public void onAnimationEnd(Animator animation) {
onAnimationFinish();
+ if (mInputMethodJankContext == null) return;
+ ImeTracker.forJank().onFinishAnimation();
}
});
if (!mHasAnimationCallbacks) {
@@ -873,7 +925,7 @@
/**
* @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
- * int, SparseIntArray)
+ * int, android.util.SparseIntArray)
*/
@VisibleForTesting
public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
@@ -981,7 +1033,7 @@
public void show(@InsetsType int types) {
ImeTracker.Token statsToken = null;
if ((types & ime()) != 0) {
- statsToken = ImeTracker.get().onRequestShow(null /* component */,
+ statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
}
@@ -1006,6 +1058,9 @@
}
// Handle pending request ready in case there was one set.
if (fromIme && mPendingImeControlRequest != null) {
+ if ((types & Type.ime()) != 0) {
+ ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
+ }
handlePendingControlRequest(statsToken);
return;
}
@@ -1028,7 +1083,7 @@
"show ignored for type: %d animType: %d requestedVisible: %s",
type, animationType, requestedVisible));
if (isImeAnimation) {
- ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.forLogging().onCancelled(statsToken,
ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
@@ -1036,16 +1091,21 @@
if (fromIme && animationType == ANIMATION_TYPE_USER) {
// App is already controlling the IME, don't cancel it.
if (isImeAnimation) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
}
if (isImeAnimation) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ ImeTracker.forLogging().onProgress(
+ statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
typesReady |= type;
}
if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
+ if (fromIme && (typesReady & Type.ime()) != 0) {
+ ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
+ }
applyAnimation(typesReady, true /* show */, fromIme, statsToken);
}
@@ -1074,7 +1134,7 @@
public void hide(@InsetsType int types) {
ImeTracker.Token statsToken = null;
if ((types & ime()) != 0) {
- statsToken = ImeTracker.get().onRequestHide(null /* component */,
+ statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
@@ -1123,13 +1183,14 @@
// no-op: already hidden or animating out (because window visibility is
// applied before starting animation).
if (isImeAnimation) {
- ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.forLogging().onCancelled(statsToken,
ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
}
if (isImeAnimation) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ ImeTracker.forLogging().onProgress(
+ statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
typesReady |= type;
}
@@ -1201,8 +1262,19 @@
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if ((types & mTypesBeingCancelled) != 0) {
+ final boolean monitoredAnimation =
+ animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
+ if (monitoredAnimation && (types & Type.ime()) != 0) {
+ if (animationType == ANIMATION_TYPE_SHOW) {
+ ImeTracker.forLatency().onShowCancelled(statsToken,
+ PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
+ } else {
+ ImeTracker.forLatency().onHideCancelled(statsToken,
+ PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
+ }
+ }
throw new IllegalStateException("Cannot start a new insets animation of "
+ Type.toString(types)
+ " while an existing " + Type.toString(mTypesBeingCancelled)
@@ -1214,7 +1286,7 @@
types &= ~mDisabledUserAnimationInsetsTypes;
if ((disabledTypes & ime()) != 0) {
- ImeTracker.get().onFailed(statsToken,
+ ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
@@ -1235,7 +1307,8 @@
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
return;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
mLastStartedAnimTypes |= types;
@@ -1302,8 +1375,11 @@
if ((typesReady & WindowInsets.Type.ime()) != 0) {
ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
mHost.getInputMethodManager(), null /* icProto */);
+ if (animationType == ANIMATION_TYPE_HIDE) {
+ ImeTracker.forLatency().onHidden(statsToken, ActivityThread::currentApplication);
+ }
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
mRunningAnimations.add(new RunningAnimation(runner, animationType));
if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
+ useInsetsAnimationThread);
@@ -1343,7 +1419,8 @@
private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
@Nullable ImeTracker.Token statsToken) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
int typesReady = 0;
boolean imeReady = true;
@@ -1446,13 +1523,13 @@
}
final ImeTracker.Token statsToken = runner.getStatsToken();
if (shown) {
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
- ImeTracker.get().onShown(statsToken);
+ ImeTracker.forLogging().onShown(statsToken);
} else {
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
- ImeTracker.get().onHidden(statsToken);
+ ImeTracker.forLogging().onHidden(statsToken);
}
reportRequestedVisibleTypes();
}
@@ -1478,13 +1555,13 @@
private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
if (invokeCallback) {
- ImeTracker.get().onCancelled(control.getStatsToken(),
- ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
+ ImeTracker.forLogging().onCancelled(control.getStatsToken(),
+ PHASE_CLIENT_ANIMATION_CANCEL);
control.cancel();
} else {
// Succeeds if invokeCallback is false (i.e. when called from notifyFinished).
- ImeTracker.get().onProgress(control.getStatsToken(),
- ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
+ ImeTracker.forLogging().onProgress(control.getStatsToken(),
+ PHASE_CLIENT_ANIMATION_CANCEL);
}
if (DEBUG) {
Log.d(TAG, TextUtils.formatSimple(
@@ -1649,7 +1726,7 @@
final InternalAnimationControlListener listener = new InternalAnimationControlListener(
show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP),
- mLoggingListener);
+ mLoggingListener, mJankContext);
// We are about to playing the default animation (show/hide). Passing a null frame indicates
// the controlled types should be animated regardless of the frame.
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
index 3e58a31..fa86a4c 100644
--- a/core/java/android/view/MotionPredictor.java
+++ b/core/java/android/view/MotionPredictor.java
@@ -25,10 +25,15 @@
import java.util.List;
/**
- * Calculates motion predictions.
+ * Calculate motion predictions.
*
- * Add motions here to get predicted events!
- * @hide
+ * Feed motion events to this class in order to generate the predicted events. The prediction
+ * functionality may not be available on all devices. Check if a specific source is supported on a
+ * given input device using #isPredictionAvailable.
+ *
+ * Send all of the events that were received from the system here in order to generate complete,
+ * accurate predictions. When processing the returned predictions, make sure to consider all of the
+ * {@link MotionEvent#getHistoricalAxisValue historical samples}.
*/
// Acts as a pass-through to the native MotionPredictor object.
// Do not store any state in this Java layer, or add any business logic here. All of the
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 00170cb..85aea85 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -19,7 +19,6 @@
import android.annotation.IntDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.input.InputManager;
-import android.os.Build;
import android.util.ArrayMap;
import android.util.Pools.SynchronizedPool;
@@ -190,8 +189,6 @@
private static native void nativeAddMovement(long ptr, MotionEvent event);
private static native void nativeComputeCurrentVelocity(long ptr, int units, float maxVelocity);
private static native float nativeGetVelocity(long ptr, int axis, int id);
- private static native boolean nativeGetEstimator(
- long ptr, int axis, int id, Estimator outEstimator);
private static native boolean nativeIsAxisSupported(int axis);
static {
@@ -436,7 +433,7 @@
* method:
* <ul>
* <li> {@link MotionEvent#AXIS_SCROLL}: supported starting
- * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
* </ul>
*
* <p>Before accessing velocities of an axis using this method, check that your
@@ -467,89 +464,4 @@
public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis) {
return nativeGetVelocity(mPtr, axis, ACTIVE_POINTER_ID);
}
-
- /**
- * Get an estimator for the movements of a pointer using past movements of the
- * pointer to predict future movements.
- *
- * It is not necessary to call {@link #computeCurrentVelocity(int)} before calling
- * this method.
- *
- * @param axis Which axis's velocity to return.
- * Should be one of the axes defined in {@link MotionEvent}.
- * @param id Which pointer's velocity to return.
- * @param outEstimator The estimator to populate.
- * @return True if an estimator was obtained, false if there is no information
- * available about the pointer.
- *
- * @hide For internal use only. Not a final API.
- */
- public boolean getEstimator(int axis, int id, Estimator outEstimator) {
- if (outEstimator == null) {
- throw new IllegalArgumentException("outEstimator must not be null");
- }
- return nativeGetEstimator(mPtr, axis, id, outEstimator);
- }
-
- /**
- * An estimator for the movements of a pointer based on a polynomial model.
- *
- * The last recorded position of the pointer is at time zero seconds.
- * Past estimated positions are at negative times and future estimated positions
- * are at positive times.
- *
- * First coefficient is position (in units), second is velocity (in units per second),
- * third is acceleration (in units per second squared).
- *
- * @hide For internal use only. Not a final API.
- */
- public static final class Estimator {
- // Must match VelocityTracker::Estimator::MAX_DEGREE
- private static final int MAX_DEGREE = 4;
-
- /**
- * Polynomial coefficients describing motion.
- */
- public final float[] coeff = new float[MAX_DEGREE + 1];
-
- /**
- * Polynomial degree, or zero if only position information is available.
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public int degree;
-
- /**
- * Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public float confidence;
-
- /**
- * Gets an estimate of the position of the pointer at the specified time point.
- * @param time The time point in seconds, 0 is the last recorded time.
- * @return The estimated axis value.
- */
- public float estimate(float time) {
- return estimate(time, coeff);
- }
-
- /**
- * Gets the coefficient with the specified index.
- * @param index The index of the coefficient to return.
- * @return The coefficient, or 0 if the index is greater than the degree.
- */
- public float getCoeff(int index) {
- return index <= degree ? coeff[index] : 0;
- }
-
- private float estimate(float time, float[] c) {
- float a = 0;
- float scale = 1;
- for (int i = 0; i <= degree; i++) {
- a += c[i] * scale;
- scale *= time;
- }
- return a;
- }
- }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c73cfc2..5ac9819 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10244,6 +10244,27 @@
return mContext.getSystemService(AutofillManager.class);
}
+ /**
+ * Check whether current activity / package is in denylist.If it's in the denylist,
+ * then the views marked as not important for autofill are not eligible for autofill.
+ */
+ final boolean isActivityDeniedForAutofillForUnimportantView() {
+ final AutofillManager afm = getAutofillManager();
+ // keep behavior same with denylist feature not enabled
+ if (afm == null) return true;
+ return afm.isActivityDeniedForAutofillForUnimportantView();
+ }
+
+ /**
+ * Check whether current view matches autofillable heuristics
+ */
+ final boolean isMatchingAutofillableHeuristics() {
+ final AutofillManager afm = getAutofillManager();
+ // keep default behavior
+ if (afm == null) return false;
+ return afm.isMatchingAutofillableHeuristics(this);
+ }
+
private boolean isAutofillable() {
if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
@@ -10252,6 +10273,12 @@
&& isCredential()) return false;
if (!isImportantForAutofill()) {
+ // If view matches heuristics and is not denied, it will be treated same as view that's
+ // important for autofill
+ if (isMatchingAutofillableHeuristics()
+ && !isActivityDeniedForAutofillForUnimportantView()) {
+ return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
+ }
// View is not important for "regular" autofill, so we must check if Augmented Autofill
// is enabled for the activity
final AutofillOptions options = mContext.getAutofillOptions();
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 46b2cfc..0e4ac01 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3723,7 +3723,9 @@
final View child = (preorderedList == null)
? mChildren[childIndex] : preorderedList.get(childIndex);
if ((flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
- || child.isImportantForAutofill()) {
+ || child.isImportantForAutofill()
+ || (child.isMatchingAutofillableHeuristics()
+ && !child.isActivityDeniedForAutofillForUnimportantView())) {
list.add(child);
} else if (child instanceof ViewGroup) {
((ViewGroup) child).populateChildrenForAutofill(list, flags);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 74e521a..2763607 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5764,7 +5764,7 @@
}
case MSG_SHOW_INSETS: {
final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS);
if (mView == null) {
Log.e(TAG,
@@ -5777,7 +5777,7 @@
}
case MSG_HIDE_INSETS: {
final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS);
mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken);
break;
@@ -10324,10 +10324,10 @@
null /* icProto */);
}
if (viewAncestor != null) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
viewAncestor.showInsets(types, fromIme, statsToken);
} else {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
}
}
@@ -10341,10 +10341,10 @@
null /* icProto */);
}
if (viewAncestor != null) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
viewAncestor.hideInsets(types, fromIme, statsToken);
} else {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
}
}
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index c59d83e..037ce87 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
import android.annotation.NonNull;
+import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.os.Handler;
import android.os.IBinder;
@@ -246,6 +247,11 @@
}
@Override
+ public Context getRootViewContext() {
+ return mViewRoot != null ? mViewRoot.getView().getContext() : null;
+ }
+
+ @Override
public int dipToPx(int dips) {
if (mViewRoot != null) {
return mViewRoot.dipToPx(dips);
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 59ad151..cba399f 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -16,13 +16,18 @@
package android.view.autofill;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.provider.DeviceConfig;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.view.View;
import com.android.internal.util.ArrayUtils;
+import java.util.Arrays;
+import java.util.Set;
+
/**
* Feature flags associated with autofill.
* @hide
@@ -119,8 +124,6 @@
public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG =
"autofill_credential_manager_suppress_fill_dialog";
-
-
/**
* Indicates whether credential manager tagged views should suppress save dialog.
* This flag is further gated by {@link #DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED}
@@ -131,6 +134,50 @@
"autofill_credential_manager_suppress_save_dialog";
// END CREDENTIAL MANAGER FLAGS //
+ // START AUTOFILL FOR ALL APPS FLAGS //
+ /**
+ * Sets the list of activities and packages denied for autofill
+ *
+ * The list is {@code ";"} colon delimited. Activities under a package is separated by
+ * {@code ","}. Each package name much be followed by a {@code ":"}. Each package entry must be
+ * ends with a {@code ";"}
+ *
+ * <p>For example, a list with only 1 package would be, {@code Package1:;}. A list with one
+ * denied activity {@code Activity1} under {@code Package1} and a full denied package
+ * {@code Package2} would be {@code Package1:Activity1;Package2:;}
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW =
+ "package_deny_list_for_unimportant_view";
+
+ /**
+ * Whether the heuristics check for view is enabled
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW =
+ "trigger_fill_request_on_unimportant_view";
+
+ /**
+ * Continas imeAction ids that is irrelevant for autofill. For example, ime_action_search. We
+ * use this to avoid trigger fill request on unimportant views.
+ *
+ * The list is {@code ","} delimited.
+ *
+ * <p> For example, a imeAction list could be "2,3,4", corresponding to ime_action definition
+ * in {@link android.view.inputmethod.EditorInfo.java}</p>
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("IntentName")
+ public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS =
+ "non_autofillable_ime_action_ids";
+ // END AUTOFILL FOR ALL APPS FLAGS //
+
/**
* Sets a value of delay time to show up the inline tooltip view.
*
@@ -221,4 +268,38 @@
DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG,
DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_DIALOG);
}
+
+ /**
+ * Whether triggering fill request on unimportant view is enabled.
+ *
+ * @hide
+ */
+ public static boolean isTriggerFillRequestOnUnimportantViewEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW, false);
+ }
+
+ /**
+ * Get the non-autofillable ime actions from flag. This will be used in filtering
+ * condition to trigger fill request.
+ *
+ * @hide
+ */
+ public static Set<String> getNonAutofillableImeActionIdSetFromFlag() {
+ final String mNonAutofillableImeActions = DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_AUTOFILL, DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS, "");
+ return new ArraySet<>(Arrays.asList(mNonAutofillableImeActions.split(",")));
+ }
+
+ /**
+ * Get denylist string from flag
+ *
+ * @hide
+ */
+ public static String getDenylistStringFromFlag() {
+ return DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW, "");
+ }
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 58e7a70..2ad01ed 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -86,8 +86,13 @@
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputMethodManager;
+import android.widget.CheckBox;
+import android.widget.DatePicker;
import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.TimePicker;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
@@ -657,6 +662,23 @@
private final boolean mIsFillDialogEnabled;
+ // Indicate whether trigger fill request on unimportant views is enabled
+ private boolean mIsTriggerFillRequestOnUnimportantViewEnabled = false;
+
+ // A set containing all non-autofillable ime actions passed by flag
+ private Set<String> mNonAutofillableImeActionIdSet = new ArraySet<>();
+
+ // If a package is fully denied, then all views that marked as not
+ // important for autofill will not trigger fill request
+ private boolean mIsPackageFullyDeniedForAutofillForUnimportantView = false;
+
+ // If a package is partially denied, autofill manager will check whether
+ // current activity is in deny set to decide whether to trigger fill request
+ private boolean mIsPackagePartiallyDeniedForAutofillForUnimportantView = false;
+
+ // A deny set read from device config
+ private Set<String> mDeniedActivitiySet = new ArraySet<>();
+
// Indicates whether called the showAutofillDialog() method.
private boolean mShowAutofillDialogCalled = false;
@@ -816,8 +838,133 @@
sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0;
sVerbose = (mOptions.loggingLevel & FLAG_ADD_CLIENT_VERBOSE) != 0;
}
+
+ mIsTriggerFillRequestOnUnimportantViewEnabled =
+ AutofillFeatureFlags.isTriggerFillRequestOnUnimportantViewEnabled();
+
+ mNonAutofillableImeActionIdSet =
+ AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag();
+
+ final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag();
+
+ final String packageName = mContext.getPackageName();
+
+ mIsPackageFullyDeniedForAutofillForUnimportantView =
+ isPackageFullyDeniedForAutofillForUnimportantView(denyListString, packageName);
+
+ mIsPackagePartiallyDeniedForAutofillForUnimportantView =
+ isPackagePartiallyDeniedForAutofillForUnimportantView(denyListString, packageName);
+
+ if (mIsPackagePartiallyDeniedForAutofillForUnimportantView) {
+ setDeniedActivitySetWithDenyList(denyListString, packageName);
+ }
}
+ private boolean isPackageFullyDeniedForAutofillForUnimportantView(
+ @NonNull String denyListString, @NonNull String packageName) {
+ // If "PackageName:;" is in the string, then it means the package name is in denylist
+ // and there are no activities specified under it. That means the package is fully
+ // denied for autofill
+ return denyListString.indexOf(packageName + ":;") != -1;
+ }
+
+ private boolean isPackagePartiallyDeniedForAutofillForUnimportantView(
+ @NonNull String denyListString, @NonNull String packageName) {
+ // This check happens after checking package is not fully denied. If "PackageName:" instead
+ // is in denylist, then it means there are specific activities to be denied. So the package
+ // is partially denied for autofill
+ return denyListString.indexOf(packageName + ":") != -1;
+ }
+
+ /**
+ * Get the denied activitiy names under specified package from denylist and set it in field
+ * mDeniedActivitiySet
+ *
+ * If using parameter as the example below, the denied activity set would be set to
+ * Set{Activity1,Activity2}.
+ *
+ * @param denyListString Denylist that is got from device config. For example,
+ * "Package1:Activity1,Activity2;Package2:;"
+ * @param packageName Specify to extract activities under which package.For example,
+ * "Package1:;"
+ */
+ private void setDeniedActivitySetWithDenyList(
+ @NonNull String denyListString, @NonNull String packageName) {
+ // 1. Get the index of where the Package name starts
+ final int packageInStringIndex = denyListString.indexOf(packageName + ":");
+
+ // 2. Get the ";" index after this index of package
+ final int firstNextSemicolonIndex = denyListString.indexOf(";", packageInStringIndex);
+
+ // 3. Get the activity names substring between the indexes
+ final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
+ if (activityStringStartIndex < firstNextSemicolonIndex) {
+ Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
+ + "formatted");
+ }
+ final String activitySubstring =
+ denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
+
+ // 4. Split the activity name substring
+ final String[] activityStringArray = activitySubstring.split(",");
+
+ // 5. Set the denied activity set
+ mDeniedActivitiySet = new ArraySet<>(Arrays.asList(activityStringArray));
+
+ return;
+ }
+
+ /**
+ * Check whether autofill is denied for current activity or package. Used when a view is marked
+ * as not important for autofill, if current activity or package is denied, then the view won't
+ * trigger fill request.
+ *
+ * @hide
+ */
+ public final boolean isActivityDeniedForAutofillForUnimportantView() {
+ if (mIsPackageFullyDeniedForAutofillForUnimportantView) {
+ return true;
+ }
+ if (mIsPackagePartiallyDeniedForAutofillForUnimportantView) {
+ final AutofillClient client = getClient();
+ if (client == null) {
+ return false;
+ }
+ final ComponentName clientActivity = client.autofillClientGetComponentName();
+ if (mDeniedActivitiySet.contains(clientActivity.flattenToShortString())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether view matches autofill-able heuristics
+ *
+ * @hide
+ */
+ public final boolean isMatchingAutofillableHeuristics(@NonNull View view) {
+ if (!mIsTriggerFillRequestOnUnimportantViewEnabled) {
+ return false;
+ }
+ if (view instanceof EditText) {
+ final int actionId = ((EditText) view).getImeOptions();
+ if (mNonAutofillableImeActionIdSet.contains(String.valueOf(actionId))) {
+ return false;
+ }
+ return true;
+ }
+ if (view instanceof CheckBox
+ || view instanceof Spinner
+ || view instanceof DatePicker
+ || view instanceof TimePicker
+ || view instanceof RadioGroup) {
+ return true;
+ }
+ return false;
+ }
+
+
/**
* @hide
*/
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index f08f61f..96602619 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -215,6 +215,21 @@
}
@AnyThread
+ @Nullable
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+ static InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return null;
+ }
+ try {
+ return service.getCurrentInputMethodInfoAsUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 3b6ec80..a07dedc 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,24 +16,36 @@
package android.view.inputmethod;
+import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION;
+import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
+import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
+import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
import android.util.Log;
+import android.view.InsetsController.AnimationType;
+import android.view.SurfaceControl;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.jank.InteractionJankMonitor.Configuration;
+import com.android.internal.util.LatencyTracker;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.Arrays;
+import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
@@ -385,15 +397,35 @@
void onHidden(@Nullable Token token);
/**
- * Get the singleton instance of this class.
+ * Get the singleton request tracker instance.
*
- * @return the singleton instance of this class
+ * @return the singleton request tracker instance
*/
@NonNull
- static ImeTracker get() {
+ static ImeTracker forLogging() {
return LOGGER;
}
+ /**
+ * Get the singleton jank tracker instance.
+ *
+ * @return the singleton jank tracker instance
+ */
+ @NonNull
+ static ImeJankTracker forJank() {
+ return JANK_TRACKER;
+ }
+
+ /**
+ * Get the singleton latency tracker instance.
+ *
+ * @return the singleton latency tracker instance
+ */
+ @NonNull
+ static ImeLatencyTracker forLatency() {
+ return LATENCY_TRACKER;
+ }
+
/** The singleton IME tracker instance. */
@NonNull
ImeTracker LOGGER = new ImeTracker() {
@@ -489,6 +521,12 @@
}
};
+ /** The singleton IME tracker instance for instrumenting jank metrics. */
+ ImeJankTracker JANK_TRACKER = new ImeJankTracker();
+
+ /** The singleton IME tracker instance for instrumenting latency metrics. */
+ ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
+
/** A token that tracks the progress of an IME request. */
class Token implements Parcelable {
@@ -592,4 +630,153 @@
}
}
}
+
+ /**
+ * Context related to {@link InteractionJankMonitor}.
+ */
+ interface InputMethodJankContext {
+ /**
+ * @return a context associated with a display
+ */
+ Context getDisplayContext();
+
+ /**
+ * @return a SurfaceControl is going to be monitored
+ */
+ SurfaceControl getTargetSurfaceControl();
+
+ /**
+ * @return the package name of the host
+ */
+ String getHostPackageName();
+ }
+
+ /**
+ * Context related to {@link LatencyTracker}.
+ */
+ interface InputMethodLatencyContext {
+ /**
+ * @return a context associated with current application
+ */
+ Context getAppContext();
+ }
+
+ /**
+ * A tracker instance which is in charge of communicating with {@link InteractionJankMonitor}.
+ */
+ final class ImeJankTracker {
+
+ private ImeJankTracker() {
+ }
+
+ /**
+ * Called when the animation, which is going to be monitored, starts.
+ *
+ * @param jankContext context which is needed by {@link InteractionJankMonitor}
+ * @param animType {@link AnimationType}
+ * @param useSeparatedThread {@code true} if the animation is handled by the app,
+ * {@code false} if the animation will be scheduled on the
+ * {@link android.view.InsetsAnimationThread}
+ */
+ public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
+ @AnimationType int animType, boolean useSeparatedThread) {
+ if (jankContext.getDisplayContext() == null
+ || jankContext.getTargetSurfaceControl() == null) {
+ return;
+ }
+ final Configuration.Builder builder = Configuration.Builder.withSurface(
+ CUJ_IME_INSETS_ANIMATION,
+ jankContext.getDisplayContext(),
+ jankContext.getTargetSurfaceControl())
+ .setTag(String.format(Locale.US, "%d@%d@%s", animType,
+ useSeparatedThread ? 0 : 1, jankContext.getHostPackageName()));
+ InteractionJankMonitor.getInstance().begin(builder);
+ }
+
+ /**
+ * Called when the animation, which is going to be monitored, cancels.
+ */
+ public void onCancelAnimation() {
+ InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION);
+ }
+
+ /**
+ * Called when the animation, which is going to be monitored, ends.
+ */
+ public void onFinishAnimation() {
+ InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION);
+ }
+ }
+
+ /**
+ * A tracker instance which is in charge of communicating with {@link LatencyTracker}.
+ */
+ final class ImeLatencyTracker {
+
+ private ImeLatencyTracker() {
+ }
+
+ private boolean shouldMonitorLatency(@SoftInputShowHideReason int reason) {
+ return reason == SoftInputShowHideReason.SHOW_SOFT_INPUT
+ || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT
+ || reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API
+ || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API
+ || reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME
+ || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME;
+ }
+
+ public void onRequestShow(@Nullable Token token, @Origin int origin,
+ @SoftInputShowHideReason int reason,
+ @NonNull InputMethodLatencyContext latencyContext) {
+ if (!shouldMonitorLatency(reason)) return;
+ LatencyTracker.getInstance(latencyContext.getAppContext())
+ .onActionStart(
+ ACTION_REQUEST_IME_SHOWN,
+ softInputDisplayReasonToString(reason));
+ }
+
+ public void onRequestHide(@Nullable Token token, @Origin int origin,
+ @SoftInputShowHideReason int reason,
+ @NonNull InputMethodLatencyContext latencyContext) {
+ if (!shouldMonitorLatency(reason)) return;
+ LatencyTracker.getInstance(latencyContext.getAppContext())
+ .onActionStart(
+ ACTION_REQUEST_IME_HIDDEN,
+ softInputDisplayReasonToString(reason));
+ }
+
+ public void onShowFailed(@Nullable Token token, @Phase int phase,
+ @NonNull InputMethodLatencyContext latencyContext) {
+ onShowCancelled(token, phase, latencyContext);
+ }
+
+ public void onHideFailed(@Nullable Token token, @Phase int phase,
+ @NonNull InputMethodLatencyContext latencyContext) {
+ onHideCancelled(token, phase, latencyContext);
+ }
+
+ public void onShowCancelled(@Nullable Token token, @Phase int phase,
+ @NonNull InputMethodLatencyContext latencyContext) {
+ LatencyTracker.getInstance(latencyContext.getAppContext())
+ .onActionCancel(ACTION_REQUEST_IME_SHOWN);
+ }
+
+ public void onHideCancelled(@Nullable Token token, @Phase int phase,
+ @NonNull InputMethodLatencyContext latencyContext) {
+ LatencyTracker.getInstance(latencyContext.getAppContext())
+ .onActionCancel(ACTION_REQUEST_IME_HIDDEN);
+ }
+
+ public void onShown(@Nullable Token token,
+ @NonNull InputMethodLatencyContext latencyContext) {
+ LatencyTracker.getInstance(latencyContext.getAppContext())
+ .onActionEnd(ACTION_REQUEST_IME_SHOWN);
+ }
+
+ public void onHidden(@Nullable Token token,
+ @NonNull InputMethodLatencyContext latencyContext) {
+ LatencyTracker.getInstance(latencyContext.getAppContext())
+ .onActionEnd(ACTION_REQUEST_IME_HIDDEN);
+ }
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index b7da732..229cc02 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -339,6 +339,28 @@
mIsVrOnly = isVrOnly;
}
+ /**
+ * @hide
+ */
+ public InputMethodInfo(InputMethodInfo source) {
+ mId = source.mId;
+ mSettingsActivityName = source.mSettingsActivityName;
+ mIsDefaultResId = source.mIsDefaultResId;
+ mIsAuxIme = source.mIsAuxIme;
+ mSupportsSwitchingToNextInputMethod = source.mSupportsSwitchingToNextInputMethod;
+ mInlineSuggestionsEnabled = source.mInlineSuggestionsEnabled;
+ mSupportsInlineSuggestionsWithTouchExploration =
+ source.mSupportsInlineSuggestionsWithTouchExploration;
+ mSuppressesSpellChecker = source.mSuppressesSpellChecker;
+ mShowInInputMethodPicker = source.mShowInInputMethodPicker;
+ mIsVrOnly = source.mIsVrOnly;
+ mService = source.mService;
+ mSubtypes = source.mSubtypes;
+ mHandledConfigChanges = source.mHandledConfigChanges;
+ mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
+ mForceDefault = source.mForceDefault;
+ }
+
InputMethodInfo(Parcel source) {
mId = source.readString();
mSettingsActivityName = source.readString();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c1b6cda..642182b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -43,6 +43,8 @@
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UiThread;
@@ -1598,6 +1600,37 @@
}
/**
+ * Returns the {@link InputMethodInfo} of the currently selected input method (for the process's
+ * user).
+ *
+ * <p>On multi user environment, this API returns a result for the calling process user.</p>
+ */
+ @Nullable
+ public InputMethodInfo getCurrentInputMethodInfo() {
+ // We intentionally do not use UserHandle.getCallingUserId() here because for system
+ // services InputMethodManagerInternal.getCurrentInputMethodInfoForUser() should be used
+ // instead.
+ return IInputMethodManagerGlobalInvoker.getCurrentInputMethodInfoAsUser(
+ UserHandle.myUserId());
+ }
+
+ /**
+ * Returns the {@link InputMethodInfo} for currently selected input method for the given user.
+ *
+ * @param user user to query.
+ * @hide
+ */
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @Nullable
+ @SystemApi
+ @SuppressLint("UserHandle")
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull UserHandle user) {
+ Objects.requireNonNull(user);
+ return IInputMethodManagerGlobalInvoker.getCurrentInputMethodInfoAsUser(
+ user.getIdentifier());
+ }
+
+ /**
* Returns the list of enabled input methods.
*
* <p>On multi user environment, this API returns a result for the calling process user.</p>
@@ -2012,10 +2045,11 @@
private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
if (statsToken == null) {
- statsToken = ImeTracker.get().onRequestShow(null /* component */,
+ statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
}
-
+ ImeTracker.forLatency().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ reason, ActivityThread::currentApplication);
ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
null /* icProto */);
// Re-dispatch if there is a context mismatch.
@@ -2027,12 +2061,15 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view)) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLatency().onShowFailed(
+ statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED,
+ ActivityThread::currentApplication);
Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
return false;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2062,20 +2099,20 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
synchronized (mH) {
- final ImeTracker.Token statsToken = ImeTracker.get().onRequestShow(null /* component */,
- Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
+ null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT);
Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
+ " removed soon. If you are using androidx.appcompat.widget.SearchView,"
+ " please update to version 26.0 or newer version.");
if (mCurRootView == null || mCurRootView.getView() == null) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
return;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2153,20 +2190,24 @@
private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
- Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
-
+ final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
+ null /* component */, Process.myUid(),
+ ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
+ ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ reason, ActivityThread::currentApplication);
ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
this, null /* icProto */);
checkFocus();
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLatency().onHideFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
return false;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
flags, resultReceiver, reason);
@@ -2802,18 +2843,22 @@
@UnsupportedAppUsage
void closeCurrentInput() {
- final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
- Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
+ null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
+ ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT, ActivityThread::currentApplication);
synchronized (mH) {
if (mCurRootView == null || mCurRootView.getView() == null) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLatency().onHideFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
return;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
IInputMethodManagerGlobalInvoker.hideSoftInput(
mClient,
@@ -2872,11 +2917,13 @@
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
return false;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
@@ -2894,21 +2941,25 @@
*/
public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
if (statsToken == null) {
- statsToken = ImeTracker.get().onRequestHide(null /* component */,
+ statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
-
+ ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
+ ActivityThread::currentApplication);
ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
null /* icProto */);
synchronized (mH) {
if (!isImeSessionAvailableLocked() || mCurRootView == null
|| mCurRootView.getWindowToken() != windowToken) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLatency().onHideFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
return;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
0 /* flags */, null /* resultReceiver */,
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b33afa5..95a42aa 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -76,6 +76,7 @@
import android.text.method.KeyListener;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
+import android.text.method.OffsetMapping;
import android.text.method.WordIterator;
import android.text.style.EasyEditSpan;
import android.text.style.SuggestionRangeSpan;
@@ -571,7 +572,7 @@
mTextView.getContext().getResources().getDisplayMetrics());
final Layout layout = mTextView.getLayout();
- final int line = layout.getLineForOffset(mTextView.getSelectionStart());
+ final int line = layout.getLineForOffset(mTextView.getSelectionStartTransformed());
final int sourceHeight = layout.getLineBottom(line, /* includeLineSpacing= */ false)
- layout.getLineTop(line);
final int height = (int)(sourceHeight * zoom);
@@ -1279,12 +1280,16 @@
* Get the minimum range of paragraphs that contains startOffset and endOffset.
*/
private long getParagraphsRange(int startOffset, int endOffset) {
+ final int startOffsetTransformed = mTextView.originalToTransformed(startOffset,
+ OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int endOffsetTransformed = mTextView.originalToTransformed(endOffset,
+ OffsetMapping.MAP_STRATEGY_CURSOR);
final Layout layout = mTextView.getLayout();
if (layout == null) {
return TextUtils.packRangeInLong(-1, -1);
}
- final CharSequence text = mTextView.getText();
- int minLine = layout.getLineForOffset(startOffset);
+ final CharSequence text = layout.getText();
+ int minLine = layout.getLineForOffset(startOffsetTransformed);
// Search paragraph start.
while (minLine > 0) {
final int prevLineEndOffset = layout.getLineEnd(minLine - 1);
@@ -1293,7 +1298,7 @@
}
minLine--;
}
- int maxLine = layout.getLineForOffset(endOffset);
+ int maxLine = layout.getLineForOffset(endOffsetTransformed);
// Search paragraph end.
while (maxLine < layout.getLineCount() - 1) {
final int lineEndOffset = layout.getLineEnd(maxLine);
@@ -1302,7 +1307,11 @@
}
maxLine++;
}
- return TextUtils.packRangeInLong(layout.getLineStart(minLine), layout.getLineEnd(maxLine));
+ final int paragraphStart = mTextView.transformedToOriginal(layout.getLineStart(minLine),
+ OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int paragraphEnd = mTextView.transformedToOriginal(layout.getLineEnd(maxLine),
+ OffsetMapping.MAP_STRATEGY_CURSOR);
+ return TextUtils.packRangeInLong(paragraphStart, paragraphEnd);
}
void onLocaleChanged() {
@@ -1339,8 +1348,16 @@
private int getNextCursorOffset(int offset, boolean findAfterGivenOffset) {
final Layout layout = mTextView.getLayout();
if (layout == null) return offset;
- return findAfterGivenOffset == layout.isRtlCharAt(offset)
- ? layout.getOffsetToLeftOf(offset) : layout.getOffsetToRightOf(offset);
+ final int offsetTransformed =
+ mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int nextCursor;
+ if (findAfterGivenOffset == layout.isRtlCharAt(offsetTransformed)) {
+ nextCursor = layout.getOffsetToLeftOf(offsetTransformed);
+ } else {
+ nextCursor = layout.getOffsetToRightOf(offsetTransformed);
+ }
+
+ return mTextView.transformedToOriginal(nextCursor, OffsetMapping.MAP_STRATEGY_CURSOR);
}
private long getCharClusterRange(int offset) {
@@ -1396,9 +1413,11 @@
Layout layout = mTextView.getLayout();
if (layout == null) return false;
- final int line = layout.getLineForOffset(offset);
+ final int offsetTransformed =
+ mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int line = layout.getLineForOffset(offsetTransformed);
final int lineBottom = layout.getLineBottom(line);
- final int primaryHorizontal = (int) layout.getPrimaryHorizontal(offset);
+ final int primaryHorizontal = (int) layout.getPrimaryHorizontal(offsetTransformed);
return mTextView.isPositionVisible(
primaryHorizontal + mTextView.viewportToContentHorizontalOffset(),
lineBottom + mTextView.viewportToContentVerticalOffset());
@@ -2300,8 +2319,12 @@
*/
void invalidateTextDisplayList(Layout layout, int start, int end) {
if (mTextRenderNodes != null && layout instanceof DynamicLayout) {
- final int firstLine = layout.getLineForOffset(start);
- final int lastLine = layout.getLineForOffset(end);
+ final int startTransformed =
+ mTextView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CHARACTER);
+ final int endTransformed =
+ mTextView.originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CHARACTER);
+ final int firstLine = layout.getLineForOffset(startTransformed);
+ final int lastLine = layout.getLineForOffset(endTransformed);
DynamicLayout dynamicLayout = (DynamicLayout) layout;
int[] blockEndLines = dynamicLayout.getBlockEndLines();
@@ -2344,12 +2367,14 @@
final Layout layout = mTextView.getLayout();
final int offset = mTextView.getSelectionStart();
- final int line = layout.getLineForOffset(offset);
+ final int transformedOffset = mTextView.originalToTransformed(offset,
+ OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int line = layout.getLineForOffset(transformedOffset);
final int top = layout.getLineTop(line);
final int bottom = layout.getLineBottom(line, /* includeLineSpacing= */ false);
final boolean clamped = layout.shouldClampCursor(line);
- updateCursorPosition(top, bottom, layout.getPrimaryHorizontal(offset, clamped));
+ updateCursorPosition(top, bottom, layout.getPrimaryHorizontal(transformedOffset, clamped));
}
void refreshTextActionMode() {
@@ -3628,10 +3653,14 @@
measureContent();
final int width = mContentView.getMeasuredWidth();
final int offset = getTextOffset();
- mPositionX = (int) (mTextView.getLayout().getPrimaryHorizontal(offset) - width / 2.0f);
+ final int transformedOffset = mTextView.originalToTransformed(offset,
+ OffsetMapping.MAP_STRATEGY_CURSOR);
+ final Layout layout = mTextView.getLayout();
+
+ mPositionX = (int) (layout.getPrimaryHorizontal(transformedOffset) - width / 2.0f);
mPositionX += mTextView.viewportToContentHorizontalOffset();
- final int line = mTextView.getLayout().getLineForOffset(offset);
+ final int line = layout.getLineForOffset(transformedOffset);
mPositionY = getVerticalLocalPosition(line);
mPositionY += mTextView.viewportToContentVerticalOffset();
}
@@ -4564,19 +4593,20 @@
super.onGetContentRect(mode, view, outRect);
return;
}
- if (mTextView.getSelectionStart() != mTextView.getSelectionEnd()) {
+ final int selectionStart = mTextView.getSelectionStartTransformed();
+ final int selectionEnd = mTextView.getSelectionEndTransformed();
+ final Layout layout = mTextView.getLayout();
+ if (selectionStart != selectionEnd) {
// We have a selection.
mSelectionPath.reset();
- mTextView.getLayout().getSelectionPath(
- mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
+ layout.getSelectionPath(selectionStart, selectionEnd, mSelectionPath);
mSelectionPath.computeBounds(mSelectionBounds, true);
mSelectionBounds.bottom += mHandleHeight;
} else {
// We have a cursor.
- Layout layout = mTextView.getLayout();
- int line = layout.getLineForOffset(mTextView.getSelectionStart());
- float primaryHorizontal = clampHorizontalPosition(null,
- layout.getPrimaryHorizontal(mTextView.getSelectionStart()));
+ int line = layout.getLineForOffset(selectionStart);
+ float primaryHorizontal =
+ clampHorizontalPosition(null, layout.getPrimaryHorizontal(selectionEnd));
mSelectionBounds.set(
primaryHorizontal,
layout.getLineTop(line),
@@ -4679,8 +4709,9 @@
mTextView.viewportToContentHorizontalOffset();
final float viewportToContentVerticalOffset =
mTextView.viewportToContentVerticalOffset();
-
- if (includeCharacterBounds) {
+ final boolean isTextTransformed = (mTextView.getTransformationMethod() != null
+ && mTextView.getTransformed() instanceof OffsetMapping);
+ if (includeCharacterBounds && !isTextTransformed) {
final CharSequence text = mTextView.getText();
if (text instanceof Spannable) {
final Spannable sp = (Spannable) text;
@@ -4708,10 +4739,12 @@
if (includeInsertionMarker) {
// Treat selectionStart as the insertion point.
if (0 <= selectionStart) {
- final int offset = selectionStart;
- final int line = layout.getLineForOffset(offset);
- final float insertionMarkerX = layout.getPrimaryHorizontal(offset)
- + viewportToContentHorizontalOffset;
+ final int offsetTransformed = mTextView.originalToTransformed(
+ selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int line = layout.getLineForOffset(offsetTransformed);
+ final float insertionMarkerX =
+ layout.getPrimaryHorizontal(offsetTransformed)
+ + viewportToContentHorizontalOffset;
final float insertionMarkerTop = layout.getLineTop(line)
+ viewportToContentVerticalOffset;
final float insertionMarkerBaseline = layout.getLineBaseline(line)
@@ -4730,7 +4763,7 @@
if (!isTopVisible || !isBottomVisible) {
insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
}
- if (layout.isRtlCharAt(offset)) {
+ if (layout.isRtlCharAt(offsetTransformed)) {
insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
}
builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
@@ -5107,12 +5140,28 @@
protected abstract int getMagnifierHandleTrigger();
protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
- return layout.isRtlCharAt(offset);
+ final int transformedOffset =
+ mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+ return layout.isRtlCharAt(transformedOffset);
}
@VisibleForTesting
public float getHorizontal(@NonNull Layout layout, int offset) {
- return layout.getPrimaryHorizontal(offset);
+ final int transformedOffset =
+ mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+ return layout.getPrimaryHorizontal(transformedOffset);
+ }
+
+ /**
+ * Return the line number for a given offset.
+ * @param layout the {@link Layout} to query.
+ * @param offset the index of the character to query.
+ * @return the index of the line the given offset belongs to.
+ */
+ public int getLineForOffset(@NonNull Layout layout, int offset) {
+ final int transformedOffset =
+ mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
+ return layout.getLineForOffset(transformedOffset);
}
protected int getOffsetAtCoordinate(@NonNull Layout layout, int line, float x) {
@@ -5129,13 +5178,12 @@
protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition,
boolean fromTouchScreen) {
// A HandleView relies on the layout, which may be nulled by external methods
- Layout layout = mTextView.getLayout();
+ final Layout layout = mTextView.getLayout();
if (layout == null) {
// Will update controllers' state, hiding them and stopping selection mode if needed
prepareCursorControllers();
return;
}
- layout = mTextView.getLayout();
boolean offsetChanged = offset != mPreviousOffset;
if (offsetChanged || forceUpdatePosition) {
@@ -5146,7 +5194,7 @@
}
addPositionToTouchUpFilter(offset);
}
- final int line = layout.getLineForOffset(offset);
+ final int line = getLineForOffset(layout, offset);
mPrevLine = line;
mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX
@@ -5246,7 +5294,7 @@
private boolean tooLargeTextForMagnifier() {
if (mNewMagnifierEnabled) {
Layout layout = mTextView.getLayout();
- final int line = layout.getLineForOffset(getCurrentCursorOffset());
+ final int line = getLineForOffset(layout, getCurrentCursorOffset());
return layout.getLineBottom(line, /* includeLineSpacing= */ false)
- layout.getLineTop(line) >= mMaxLineHeightForMagnifier;
}
@@ -5337,11 +5385,11 @@
}
final Layout layout = mTextView.getLayout();
- final int lineNumber = layout.getLineForOffset(offset);
+ final int lineNumber = getLineForOffset(layout, offset);
// Compute whether the selection handles are currently on the same line, and,
// in this particular case, whether the selected text is right to left.
final boolean sameLineSelection = otherHandleOffset != -1
- && lineNumber == layout.getLineForOffset(otherHandleOffset);
+ && lineNumber == getLineForOffset(layout, offset);
final boolean rtl = sameLineSelection
&& (offset < otherHandleOffset)
!= (getHorizontal(mTextView.getLayout(), offset)
@@ -5468,7 +5516,7 @@
if (mNewMagnifierEnabled) {
// Calculates the line bounds as the content source bounds to the magnifier.
Layout layout = mTextView.getLayout();
- int line = layout.getLineForOffset(getCurrentCursorOffset());
+ int line = getLineForOffset(layout, getCurrentCursorOffset());
int lineLeft = (int) layout.getLineLeft(line);
lineLeft += mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
int lineRight = (int) layout.getLineRight(line);
@@ -5838,7 +5886,7 @@
private MotionEvent transformEventForTouchThrough(MotionEvent ev) {
final Layout layout = mTextView.getLayout();
- final int line = layout.getLineForOffset(getCurrentCursorOffset());
+ final int line = getLineForOffset(layout, getCurrentCursorOffset());
final int textHeight = layout.getLineBottom(line, /* includeLineSpacing= */ false)
- layout.getLineTop(line);
// Transforms the touch events to screen coordinates.
@@ -6050,7 +6098,7 @@
|| !isStartHandle() && initialOffset <= anotherHandleOffset) {
// Handles have crossed, bound it to the first selected line and
// adjust by word / char as normal.
- currLine = layout.getLineForOffset(anotherHandleOffset);
+ currLine = getLineForOffset(layout, anotherHandleOffset);
initialOffset = getOffsetAtCoordinate(layout, currLine, x);
}
@@ -6065,7 +6113,8 @@
final int currentOffset = getCurrentCursorOffset();
final boolean rtlAtCurrentOffset = isAtRtlRun(layout, currentOffset);
final boolean atRtl = isAtRtlRun(layout, offset);
- final boolean isLvlBoundary = layout.isLevelBoundary(offset);
+ final boolean isLvlBoundary = layout.isLevelBoundary(
+ mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR));
// We can't determine if the user is expanding or shrinking the selection if they're
// on a bi-di boundary, so until they've moved past the boundary we'll just place
@@ -6077,7 +6126,9 @@
mTouchWordDelta = 0.0f;
positionAndAdjustForCrossingHandles(offset, fromTouchScreen);
return;
- } else if (mLanguageDirectionChanged && !isLvlBoundary) {
+ }
+
+ if (mLanguageDirectionChanged) {
// We've just moved past the boundary so update the position. After this we can
// figure out if the user is expanding or shrinking to go by word or character.
positionAndAdjustForCrossingHandles(offset, fromTouchScreen);
@@ -6129,7 +6180,7 @@
// Sometimes words can be broken across lines (Chinese, hyphenation).
// We still snap to the word boundary but we only use the letters on the
// current line to determine if the user is far enough into the word to snap.
- if (layout.getLineForOffset(wordBoundary) != currLine) {
+ if (getLineForOffset(layout, wordBoundary) != currLine) {
wordBoundary = isStartHandle()
? layout.getLineStart(currLine) : layout.getLineEnd(currLine);
}
@@ -6253,12 +6304,15 @@
final int currentOffset = getCurrentCursorOffset();
final int offsetToGetRunRange = isStartHandle()
? currentOffset : Math.max(currentOffset - 1, 0);
- final long range = layout.getRunRange(offsetToGetRunRange);
+ final long range = layout.getRunRange(mTextView.originalToTransformed(
+ offsetToGetRunRange, OffsetMapping.MAP_STRATEGY_CURSOR));
if (isStartHandle()) {
offset = TextUtils.unpackRangeStartFromLong(range);
} else {
offset = TextUtils.unpackRangeEndFromLong(range);
}
+ offset = mTextView.transformedToOriginal(offset,
+ OffsetMapping.MAP_STRATEGY_CURSOR);
positionAtCursorOffset(offset, false, fromTouchScreen);
return;
}
@@ -6285,7 +6339,10 @@
@Override
protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
- final int offsetToCheck = isStartHandle() ? offset : Math.max(offset - 1, 0);
+ final int transformedOffset =
+ mTextView.transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CHARACTER);
+ final int offsetToCheck = isStartHandle() ? transformedOffset
+ : Math.max(transformedOffset - 1, 0);
return layout.isRtlCharAt(offsetToCheck);
}
@@ -6295,12 +6352,17 @@
}
private float getHorizontal(@NonNull Layout layout, int offset, boolean startHandle) {
- final int line = layout.getLineForOffset(offset);
- final int offsetToCheck = startHandle ? offset : Math.max(offset - 1, 0);
+ final int offsetTransformed = mTextView.originalToTransformed(offset,
+ OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int line = layout.getLineForOffset(offsetTransformed);
+ final int offsetToCheck =
+ startHandle ? offsetTransformed : Math.max(offsetTransformed - 1, 0);
final boolean isRtlChar = layout.isRtlCharAt(offsetToCheck);
final boolean isRtlParagraph = layout.getParagraphDirection(line) == -1;
- return (isRtlChar == isRtlParagraph)
- ? layout.getPrimaryHorizontal(offset) : layout.getSecondaryHorizontal(offset);
+ if (isRtlChar != isRtlParagraph) {
+ return layout.getSecondaryHorizontal(offsetTransformed);
+ }
+ return layout.getPrimaryHorizontal(offsetTransformed);
}
@Override
@@ -6308,23 +6370,27 @@
final float localX = mTextView.convertToLocalHorizontalCoordinate(x);
final int primaryOffset = layout.getOffsetForHorizontal(line, localX, true);
if (!layout.isLevelBoundary(primaryOffset)) {
- return primaryOffset;
+ return mTextView.transformedToOriginal(primaryOffset,
+ OffsetMapping.MAP_STRATEGY_CURSOR);
}
final int secondaryOffset = layout.getOffsetForHorizontal(line, localX, false);
- final int currentOffset = getCurrentCursorOffset();
+ final int currentOffset = mTextView.originalToTransformed(getCurrentCursorOffset(),
+ OffsetMapping.MAP_STRATEGY_CURSOR);
final int primaryDiff = Math.abs(primaryOffset - currentOffset);
final int secondaryDiff = Math.abs(secondaryOffset - currentOffset);
+ final int offset;
if (primaryDiff < secondaryDiff) {
- return primaryOffset;
+ offset = primaryOffset;
} else if (primaryDiff > secondaryDiff) {
- return secondaryOffset;
+ offset = secondaryOffset;
} else {
final int offsetToCheck = isStartHandle()
? currentOffset : Math.max(currentOffset - 1, 0);
final boolean isRtlChar = layout.isRtlCharAt(offsetToCheck);
final boolean isRtlParagraph = layout.getParagraphDirection(line) == -1;
- return isRtlChar == isRtlParagraph ? primaryOffset : secondaryOffset;
+ offset = (isRtlChar == isRtlParagraph) ? primaryOffset : secondaryOffset;
}
+ return mTextView.transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
}
@MagnifierHandleTrigger
@@ -7165,7 +7231,10 @@
int end = Math.min(length, mEnd);
mPath.reset();
- layout.getSelectionPath(start, end, mPath);
+ layout.getSelectionPath(
+ mTextView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CHARACTER),
+ mTextView.originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CHARACTER),
+ mPath);
return true;
}
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 075aa6c..c1800cf 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -33,6 +33,7 @@
import android.text.Selection;
import android.text.Spannable;
import android.text.TextUtils;
+import android.text.method.OffsetMapping;
import android.text.util.Linkify;
import android.util.Log;
import android.view.ActionMode;
@@ -329,8 +330,6 @@
private void startSelectionActionModeWithSmartSelectAnimation(
@Nullable SelectionResult result) {
- final Layout layout = mTextView.getLayout();
-
final Runnable onAnimationEndCallback = () -> {
final SelectionResult startSelectionResult;
if (result != null && result.mStart >= 0 && result.mEnd <= getText(mTextView).length()
@@ -352,7 +351,7 @@
}
final List<SmartSelectSprite.RectangleWithTextSelectionLayout> selectionRectangles =
- convertSelectionToRectangles(layout, result.mStart, result.mEnd);
+ convertSelectionToRectangles(mTextView, result.mStart, result.mEnd);
final PointF touchPoint = new PointF(
mEditor.getLastUpPositionX(),
@@ -369,7 +368,7 @@
}
private List<SmartSelectSprite.RectangleWithTextSelectionLayout> convertSelectionToRectangles(
- final Layout layout, final int start, final int end) {
+ final TextView textView, final int start, final int end) {
final List<SmartSelectSprite.RectangleWithTextSelectionLayout> result = new ArrayList<>();
final Layout.SelectionRectangleConsumer consumer =
@@ -381,7 +380,11 @@
textSelectionLayout)
);
- layout.getSelection(start, end, consumer);
+ final int startTransformed =
+ textView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int endTransformed =
+ textView.originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+ textView.getLayout().getSelection(startTransformed, endTransformed, consumer);
result.sort(Comparator.comparing(
SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d6c2d30..6ff808e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -135,6 +135,7 @@
import android.text.method.LinkMovementMethod;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
+import android.text.method.OffsetMapping;
import android.text.method.PasswordTransformationMethod;
import android.text.method.SingleLineTransformationMethod;
import android.text.method.TextKeyListener;
@@ -456,6 +457,14 @@
private static final int CHANGE_WATCHER_PRIORITY = 100;
+ /**
+ * The span priority of the {@link TransformationMethod} that is set on the text. It must be
+ * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
+ * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
+ * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
+ */
+ private static final int TRANSFORMATION_SPAN_PRIORITY = 200;
+
// New state used to change background based on whether this TextView is multiline.
private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
@@ -7009,7 +7018,8 @@
final int textLength = text.length();
- if (text instanceof Spannable && !mAllowTransformationLengthChange) {
+ if (text instanceof Spannable && (!mAllowTransformationLengthChange
+ || text instanceof OffsetMapping)) {
Spannable sp = (Spannable) text;
// Remove any ChangeWatchers that might have come from other TextViews.
@@ -7027,7 +7037,8 @@
if (mEditor != null) mEditor.addSpanWatchers(sp);
if (mTransformation != null) {
- sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
+ | (TRANSFORMATION_SPAN_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
}
if (mMovement != null) {
@@ -8226,6 +8237,8 @@
if (mLayout == null) {
invalidate();
} else {
+ start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+ end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
int lineStart = mLayout.getLineForOffset(start);
int top = mLayout.getLineTop(lineStart);
@@ -8715,8 +8728,8 @@
Path highlight = null;
Paint highlightPaint = mHighlightPaint;
- final int selStart = getSelectionStart();
- final int selEnd = getSelectionEnd();
+ final int selStart = getSelectionStartTransformed();
+ final int selEnd = getSelectionEndTransformed();
if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
if (selStart == selEnd) {
if (mEditor != null && mEditor.shouldRenderCursor()) {
@@ -8937,13 +8950,13 @@
return;
}
- int selEnd = getSelectionEnd();
+ int selEnd = getSelectionEndTransformed();
if (selEnd < 0) {
super.getFocusedRect(r);
return;
}
- int selStart = getSelectionStart();
+ int selStart = getSelectionStartTransformed();
if (selStart < 0 || selStart >= selEnd) {
int line = mLayout.getLineForOffset(selEnd);
r.top = mLayout.getLineTop(line);
@@ -9826,6 +9839,14 @@
return false;
}
+ /**
+ * Return whether the text is transformed and has {@link OffsetMapping}.
+ * @hide
+ */
+ public boolean isOffsetMappingAvailable() {
+ return mTransformation != null && mTransformed instanceof OffsetMapping;
+ }
+
/** @hide */
public boolean previewHandwritingGesture(
@NonNull PreviewableHandwritingGesture gesture,
@@ -9855,6 +9876,9 @@
}
private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
+ if (isOffsetMappingAvailable()) {
+ return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+ }
int[] range = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
gesture.getGranularity());
@@ -9881,6 +9905,9 @@
private int performHandwritingSelectRangeGesture(
@NonNull SelectRangeGesture gesture, boolean isPreview) {
+ if (isOffsetMappingAvailable()) {
+ return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+ }
int[] startRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
gesture.getGranularity());
@@ -9905,6 +9932,9 @@
}
private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
+ if (isOffsetMappingAvailable()) {
+ return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+ }
int[] range = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
gesture.getGranularity());
@@ -9935,6 +9965,9 @@
private int performHandwritingDeleteRangeGesture(
@NonNull DeleteRangeGesture gesture, boolean isPreview) {
+ if (isOffsetMappingAvailable()) {
+ return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+ }
int[] startRange = getRangeForRect(
convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
gesture.getGranularity());
@@ -10015,6 +10048,9 @@
/** @hide */
public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
+ if (isOffsetMappingAvailable()) {
+ return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+ }
PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
int line = getLineForHandwritingGesture(point);
if (line == -1) {
@@ -10030,6 +10066,9 @@
/** @hide */
public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) {
+ if (isOffsetMappingAvailable()) {
+ return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+ }
PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
@@ -10088,6 +10127,9 @@
/** @hide */
public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
+ if (isOffsetMappingAvailable()) {
+ return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+ }
PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
int line = getLineForHandwritingGesture(point);
@@ -11179,13 +11221,15 @@
mDeferScroll = offset;
return false;
}
+ final int offsetTransformed =
+ originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
boolean changed = false;
Layout layout = isShowingHint() ? mHintLayout : mLayout;
if (layout == null) return changed;
- int line = layout.getLineForOffset(offset);
+ int line = layout.getLineForOffset(offsetTransformed);
int grav;
@@ -11220,7 +11264,7 @@
// right where it is most likely to be annoying.
final boolean clamped = grav > 0;
// FIXME: Is it okay to truncate this, or should we round?
- final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
+ final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped);
final int top = layout.getLineTop(line);
final int bottom = layout.getLineTop(line + 1);
@@ -11384,8 +11428,8 @@
if (!(mText instanceof Spannable)) {
return false;
}
- int start = getSelectionStart();
- int end = getSelectionEnd();
+ int start = getSelectionStartTransformed();
+ int end = getSelectionEndTransformed();
if (start != end) {
return false;
}
@@ -11428,7 +11472,8 @@
}
if (newStart != start) {
- Selection.setSelection(mSpannable, newStart);
+ Selection.setSelection(mSpannable,
+ transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR));
return true;
}
@@ -11537,6 +11582,35 @@
}
/**
+ * Calculates the rectangles which should be highlighted to indicate a selection between start
+ * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}.
+ *
+ * @param start the starting index of the selection
+ * @param end the ending index of the selection
+ * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the
+ * generated rectangles. It will be called every time a rectangle is generated.
+ * @hide
+ */
+ public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) {
+ final int transformedStart =
+ originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+ mLayout.getSelection(transformedStart, transformedEnd, consumer);
+ }
+
+ int getSelectionStartTransformed() {
+ final int start = getSelectionStart();
+ if (start < 0) return start;
+ return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+ }
+
+ int getSelectionEndTransformed() {
+ final int end = getSelectionEnd();
+ if (end < 0) return end;
+ return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+ }
+
+ /**
* Return true iff there is a selection of nonzero length inside this text view.
*/
public boolean hasSelection() {
@@ -13186,8 +13260,12 @@
}
// Convert lines into character offsets.
- int expandedTopChar = layout.getLineStart(expandedTopLine);
- int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
+ int expandedTopChar = transformedToOriginal(
+ layout.getLineStart(expandedTopLine),
+ OffsetMapping.MAP_STRATEGY_CHARACTER);
+ int expandedBottomChar = transformedToOriginal(
+ layout.getLineEnd(expandedBottomLine),
+ OffsetMapping.MAP_STRATEGY_CHARACTER);
// Take into account selection -- if there is a selection, we need to expand
// the text we are returning to include that selection.
@@ -13230,8 +13308,10 @@
final int[] lineBaselines = new int[bottomLine - topLine + 1];
final int baselineOffset = getBaselineOffset();
for (int i = topLine; i <= bottomLine; i++) {
- lineOffsets[i - topLine] = layout.getLineStart(i);
- lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
+ lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i),
+ OffsetMapping.MAP_STRATEGY_CHARACTER);
+ lineBaselines[i - topLine] =
+ layout.getLineBaseline(i) + baselineOffset;
}
structure.setTextLines(lineOffsets, lineBaselines);
}
@@ -13537,6 +13617,11 @@
public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
int startIndex, int endIndex, float viewportToContentHorizontalOffset,
float viewportToContentVerticalOffset) {
+ if (isOffsetMappingAvailable()) {
+ // The text is transformed, and has different length, we don't support
+ // character bounds in this case yet.
+ return;
+ }
final Rect rect = new Rect();
getLocalVisibleRect(rect);
final RectF visibleRect = new RectF(rect);
@@ -13601,8 +13686,8 @@
return null;
}
final CharSequence text = layout.getText();
- if (text == null) {
- // It's impossible that a layout has no text. Check here to avoid NPE.
+ if (text == null || isOffsetMappingAvailable()) {
+ // The text is Null or the text has been transformed. Can't provide TextBoundsInfo.
return null;
}
@@ -14624,10 +14709,40 @@
int getOffsetAtCoordinate(int line, float x) {
x = convertToLocalHorizontalCoordinate(x);
- return getLayout().getOffsetForHorizontal(line, x);
+ final int offset = getLayout().getOffsetForHorizontal(line, x);
+ return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR);
}
/**
+ * Convenient method to convert an offset on the transformed text to the original text.
+ * @hide
+ */
+ public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) {
+ if (getTransformationMethod() == null) {
+ return offset;
+ }
+ if (mTransformed instanceof OffsetMapping) {
+ final OffsetMapping transformedText = (OffsetMapping) mTransformed;
+ return transformedText.transformedToOriginal(offset, strategy);
+ }
+ return offset;
+ }
+
+ /**
+ * Convenient method to convert an offset on the original text to the transformed text.
+ * @hide
+ */
+ public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) {
+ if (getTransformationMethod() == null) {
+ return offset;
+ }
+ if (mTransformed instanceof OffsetMapping) {
+ final OffsetMapping transformedText = (OffsetMapping) mTransformed;
+ return transformedText.originalToTransformed(offset, strategy);
+ }
+ return offset;
+ }
+ /**
* Handles drag events sent by the system following a call to
* {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
* startDragAndDrop()}.
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index b9ca557..88d6425 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -27,6 +27,7 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.SharedMemory;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
@@ -299,6 +300,10 @@
*/
void shutdownHotwordDetectionService();
+ void startPerceiving(in IVisualQueryDetectionVoiceInteractionCallback callback);
+
+ void stopPerceiving();
+
void startListeningFromMic(
in AudioFormat audioFormat,
in IMicrophoneHotwordDetectionVoiceInteractionCallback callback);
diff --git a/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java b/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java
new file mode 100644
index 0000000..ddd3d2c
--- /dev/null
+++ b/core/java/com/android/internal/config/appcloning/AppCloningDeviceConfigHelper.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 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.config.appcloning;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Helper class that holds the flags related to the app_cloning namespace in {@link DeviceConfig}.
+ *
+ * @hide
+ */
+public class AppCloningDeviceConfigHelper {
+
+ @GuardedBy("sLock")
+ private static AppCloningDeviceConfigHelper sInstance;
+
+ private static final Object sLock = new Object();
+
+ private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangeListener;
+
+ /**
+ * This flag is defined inside {@link DeviceConfig#NAMESPACE_APP_CLONING}. Please check
+ * {@link #mEnableAppCloningBuildingBlocks} for details.
+ */
+ public static final String ENABLE_APP_CLONING_BUILDING_BLOCKS =
+ "enable_app_cloning_building_blocks";
+
+ /**
+ * Checks whether the support for app-cloning building blocks (like contacts
+ * sharing/intent redirection), which are available starting from the U release, is turned on.
+ * The default value is true to ensure the features are always enabled going forward.
+ *
+ * TODO:(b/253449368) Add information about the app-cloning config and mention that the devices
+ * that do not support app-cloning should use the app-cloning config to disable all app-cloning
+ * features.
+ */
+ private volatile boolean mEnableAppCloningBuildingBlocks = true;
+
+ private AppCloningDeviceConfigHelper() {}
+
+ /**
+ * @hide
+ */
+ public static AppCloningDeviceConfigHelper getInstance(Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new AppCloningDeviceConfigHelper();
+ sInstance.init(context);
+ }
+ return sInstance;
+ }
+ }
+
+ private void init(Context context) {
+ initializeDeviceConfigChangeListener();
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_CLONING,
+ context.getMainExecutor(),
+ mDeviceConfigChangeListener);
+ }
+
+ private void initializeDeviceConfigChangeListener() {
+ mDeviceConfigChangeListener = properties -> {
+ if (!DeviceConfig.NAMESPACE_APP_CLONING.equals(properties.getNamespace())) {
+ return;
+ }
+ for (String name : properties.getKeyset()) {
+ if (name == null) {
+ return;
+ }
+ if (ENABLE_APP_CLONING_BUILDING_BLOCKS.equals(name)) {
+ updateEnableAppCloningBuildingBlocks();
+ }
+ }
+ };
+ }
+
+ private void updateEnableAppCloningBuildingBlocks() {
+ mEnableAppCloningBuildingBlocks = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_APP_CLONING, ENABLE_APP_CLONING_BUILDING_BLOCKS, true);
+ }
+
+ /**
+ * Fetch the feature flag to check whether the support for the app-cloning building blocks
+ * (like contacts sharing/intent redirection) is enabled on the device.
+ * @hide
+ */
+ public boolean getEnableAppCloningBuildingBlocks() {
+ return mEnableAppCloningBuildingBlocks;
+ }
+}
diff --git a/core/java/com/android/internal/config/appcloning/OWNERS b/core/java/com/android/internal/config/appcloning/OWNERS
new file mode 100644
index 0000000..0645a8c5
--- /dev/null
+++ b/core/java/com/android/internal/config/appcloning/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1207885
+jigarthakkar@google.com
+saumyap@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index d9e9a5f..95a47ea 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,11 +16,15 @@
package com.android.internal.jank;
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
@@ -89,17 +93,19 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
+import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -232,6 +238,7 @@
public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
+ public static final int CUJ_IME_INSETS_ANIMATION = 69;
private static final int NO_STATSD_LOGGING = -1;
@@ -309,6 +316,7 @@
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION,
};
private static class InstanceHolder {
@@ -401,7 +409,8 @@
CUJ_RECENTS_SCROLLING,
CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
- CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME
+ CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
+ CUJ_IME_INSETS_ANIMATION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -422,29 +431,37 @@
* @param worker the worker thread for the callbacks
*/
@VisibleForTesting
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public InteractionJankMonitor(@NonNull HandlerThread worker) {
- // Check permission early.
- Settings.Config.enforceReadPermission(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
-
mRunningTrackers = new SparseArray<>();
mTimeoutActions = new SparseArray<>();
mWorker = worker;
mWorker.start();
- mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
-
- // Post initialization to the background in case we're running on the main
- // thread.
- mWorker.getThreadHandler().post(
- () -> mPropertiesChangedListener.onPropertiesChanged(
- DeviceConfig.getProperties(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
- DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
- new HandlerExecutor(mWorker.getThreadHandler()),
- mPropertiesChangedListener);
+ mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
mEnabled = DEFAULT_ENABLED;
+
+ final Context context = ActivityThread.currentApplication();
+ if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
+ // Post initialization to the background in case we're running on the main thread.
+ mWorker.getThreadHandler().post(
+ () -> mPropertiesChangedListener.onPropertiesChanged(
+ DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
+ new HandlerExecutor(mWorker.getThreadHandler()),
+ mPropertiesChangedListener);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Initialized the InteractionJankMonitor."
+ + " (No READ_DEVICE_CONFIG permission to change configs)"
+ + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+ + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+ + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
+ + ", package=" + context.getPackageName());
+ }
+ }
}
/**
@@ -923,6 +940,8 @@
return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
+ case CUJ_IME_INSETS_ANIMATION:
+ return "IME_INSETS_ANIMATION";
}
return "UNKNOWN";
}
@@ -1178,7 +1197,7 @@
*/
@VisibleForTesting
public int getDisplayId() {
- return (mSurfaceOnly ? mContext.getDisplay() : mView.getDisplay()).getDisplayId();
+ return mSurfaceOnly ? mContext.getDisplayId() : mView.getContext().getDisplayId();
}
}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index afb526a..2d5bb6c 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -14,7 +14,10 @@
package com.android.internal.util;
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_APP;
+import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED;
@@ -24,6 +27,8 @@
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
@@ -42,9 +47,12 @@
import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.ConditionVariable;
@@ -187,6 +195,16 @@
*/
public static final int ACTION_SHOW_VOICE_INTERACTION = 19;
+ /**
+ * Time it takes to request IME shown animation.
+ */
+ public static final int ACTION_REQUEST_IME_SHOWN = 20;
+
+ /**
+ * Time it takes to request IME hidden animation.
+ */
+ public static final int ACTION_REQUEST_IME_HIDDEN = 21;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -208,6 +226,8 @@
ACTION_SHOW_SELECTION_TOOLBAR,
ACTION_FOLD_TO_AOD,
ACTION_SHOW_VOICE_INTERACTION,
+ ACTION_REQUEST_IME_SHOWN,
+ ACTION_REQUEST_IME_HIDDEN,
};
/** @hide */
@@ -232,6 +252,8 @@
ACTION_SHOW_SELECTION_TOOLBAR,
ACTION_FOLD_TO_AOD,
ACTION_SHOW_VOICE_INTERACTION,
+ ACTION_REQUEST_IME_SHOWN,
+ ACTION_REQUEST_IME_HIDDEN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -259,6 +281,8 @@
UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN,
};
private static LatencyTracker sLatencyTracker;
@@ -284,15 +308,30 @@
return sLatencyTracker;
}
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
@VisibleForTesting
public LatencyTracker() {
mEnabled = DEFAULT_ENABLED;
- // Post initialization to the background in case we're running on the main thread.
- BackgroundThread.getHandler().post(() -> this.updateProperties(
- DeviceConfig.getProperties(DeviceConfig.NAMESPACE_LATENCY_TRACKER)));
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
- BackgroundThread.getExecutor(), this::updateProperties);
+ final Context context = ActivityThread.currentApplication();
+ if (context != null
+ && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
+ // Post initialization to the background in case we're running on the main thread.
+ BackgroundThread.getHandler().post(() -> this.updateProperties(
+ DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
+ BackgroundThread.getExecutor(), this::updateProperties);
+ } else {
+ if (DEBUG) {
+ if (context == null) {
+ Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
+ } else {
+ Log.d(TAG, "Initialized the LatencyTracker."
+ + " (No READ_DEVICE_CONFIG permission to change configs)"
+ + " enabled=" + mEnabled + ", package=" + context.getPackageName());
+ }
+ }
+ }
}
private void updateProperties(DeviceConfig.Properties properties) {
@@ -368,6 +407,10 @@
return "ACTION_FOLD_TO_AOD";
case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION:
return "ACTION_SHOW_VOICE_INTERACTION";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN:
+ return "ACTION_REQUEST_IME_SHOWN";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN:
+ return "ACTION_REQUEST_IME_HIDDEN";
default:
throw new IllegalArgumentException("Invalid action");
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 2106426..9116cb3 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -40,6 +40,11 @@
// TODO: Use ParceledListSlice instead
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+ InputMethodInfo getCurrentInputMethodInfoAsUser(int userId);
+
+ // TODO: Use ParceledListSlice instead
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
List<InputMethodInfo> getInputMethodList(int userId, int directBootAwareness);
// TODO: Use ParceledListSlice instead
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index c08f264..f55d15d 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -92,12 +92,6 @@
private float mBoundingRight;
private float mBoundingBottom;
- // Position estimator.
- private VelocityTracker.Estimator mEstimatorX = new VelocityTracker.Estimator();
- private VelocityTracker.Estimator mAltEstimatorX = new VelocityTracker.Estimator();
- private VelocityTracker.Estimator mEstimatorY = new VelocityTracker.Estimator();
- private VelocityTracker.Estimator mAltEstimatorY = new VelocityTracker.Estimator();
-
@UnsupportedAppUsage
public PointerState() {
}
@@ -669,13 +663,9 @@
ps.addTrace(coords.x, coords.y, true);
ps.mXVelocity = mVelocity.getXVelocity(id);
ps.mYVelocity = mVelocity.getYVelocity(id);
- mVelocity.getEstimator(MotionEvent.AXIS_X, id, ps.mEstimatorX);
- mVelocity.getEstimator(MotionEvent.AXIS_Y, id, ps.mEstimatorY);
if (mAltVelocity != null) {
ps.mAltXVelocity = mAltVelocity.getXVelocity(id);
ps.mAltYVelocity = mAltVelocity.getYVelocity(id);
- mAltVelocity.getEstimator(MotionEvent.AXIS_X, id, ps.mAltEstimatorX);
- mAltVelocity.getEstimator(MotionEvent.AXIS_Y, id, ps.mAltEstimatorY);
}
ps.mToolType = event.getToolType(i);
diff --git a/core/java/com/android/server/backup/OWNERS b/core/java/com/android/server/backup/OWNERS
new file mode 100644
index 0000000..d99779e
--- /dev/null
+++ b/core/java/com/android/server/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 17cfbfc..3b409d4 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -541,87 +541,6 @@
indices.mData, indexCount);
}
-#define I(_i, _j) ((_j)+ 4*(_i))
-
-static
-void multiplyMM(float* r, const float* lhs, const float* rhs)
-{
- for (int i=0 ; i<4 ; i++) {
- const float rhs_i0 = rhs[ I(i,0) ];
- float ri0 = lhs[ I(0,0) ] * rhs_i0;
- float ri1 = lhs[ I(0,1) ] * rhs_i0;
- float ri2 = lhs[ I(0,2) ] * rhs_i0;
- float ri3 = lhs[ I(0,3) ] * rhs_i0;
- for (int j=1 ; j<4 ; j++) {
- const float rhs_ij = rhs[ I(i,j) ];
- ri0 += lhs[ I(j,0) ] * rhs_ij;
- ri1 += lhs[ I(j,1) ] * rhs_ij;
- ri2 += lhs[ I(j,2) ] * rhs_ij;
- ri3 += lhs[ I(j,3) ] * rhs_ij;
- }
- r[ I(i,0) ] = ri0;
- r[ I(i,1) ] = ri1;
- r[ I(i,2) ] = ri2;
- r[ I(i,3) ] = ri3;
- }
-}
-
-static
-void util_multiplyMM(JNIEnv *env, jclass clazz,
- jfloatArray result_ref, jint resultOffset,
- jfloatArray lhs_ref, jint lhsOffset,
- jfloatArray rhs_ref, jint rhsOffset) {
-
- FloatArrayHelper resultMat(env, result_ref, resultOffset, 16);
- FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
- FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 16);
-
- bool checkOK = resultMat.check() && lhs.check() && rhs.check();
-
- if ( !checkOK ) {
- return;
- }
-
- resultMat.bind();
- lhs.bind();
- rhs.bind();
-
- multiplyMM(resultMat.mData, lhs.mData, rhs.mData);
-
- resultMat.commitChanges();
-}
-
-static
-void multiplyMV(float* r, const float* lhs, const float* rhs)
-{
- mx4transform(rhs[0], rhs[1], rhs[2], rhs[3], lhs, r);
-}
-
-static
-void util_multiplyMV(JNIEnv *env, jclass clazz,
- jfloatArray result_ref, jint resultOffset,
- jfloatArray lhs_ref, jint lhsOffset,
- jfloatArray rhs_ref, jint rhsOffset) {
-
- FloatArrayHelper resultV(env, result_ref, resultOffset, 4);
- FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
- FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 4);
-
- bool checkOK = resultV.check() && lhs.check() && rhs.check();
-
- if ( !checkOK ) {
- return;
- }
-
- resultV.bind();
- lhs.bind();
- rhs.bind();
-
- multiplyMV(resultV.mData, lhs.mData, rhs.mData);
-
- resultV.commitChanges();
-}
-
// ---------------------------------------------------------------------------
// The internal format is no longer the same as pixel format, per Table 2 in
@@ -1014,11 +933,6 @@
* JNI registration
*/
-static const JNINativeMethod gMatrixMethods[] = {
- { "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM },
- { "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV },
-};
-
static const JNINativeMethod gVisibilityMethods[] = {
{ "computeBoundingSphere", "([FII[FI)V", (void*)util_computeBoundingSphere },
{ "frustumCullSpheres", "([FI[FII[III)I", (void*)util_frustumCullSpheres },
@@ -1051,7 +965,6 @@
} ClassRegistrationInfo;
static const ClassRegistrationInfo gClasses[] = {
- {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)},
{"android/opengl/Visibility", gVisibilityMethods, NELEM(gVisibilityMethods)},
{"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)},
{"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)},
diff --git a/core/jni/android_view_VelocityTracker.cpp b/core/jni/android_view_VelocityTracker.cpp
index 343e9d8..05c9f68 100644
--- a/core/jni/android_view_VelocityTracker.cpp
+++ b/core/jni/android_view_VelocityTracker.cpp
@@ -31,13 +31,6 @@
// Special constant to request the velocity of the active pointer.
static const int ACTIVE_POINTER_ID = -1;
-static struct {
- jfieldID coeff;
- jfieldID degree;
- jfieldID confidence;
-} gEstimatorClassInfo;
-
-
// --- VelocityTrackerState ---
class VelocityTrackerState {
@@ -50,7 +43,6 @@
// a subset of the supported axes.
void computeCurrentVelocity(int32_t units, float maxVelocity);
float getVelocity(int32_t axis, int32_t id);
- bool getEstimator(int32_t axis, int32_t id, VelocityTracker::Estimator* outEstimator);
private:
VelocityTracker mVelocityTracker;
@@ -80,11 +72,6 @@
return mComputedVelocity.getVelocity(axis, id).value_or(0);
}
-bool VelocityTrackerState::getEstimator(int32_t axis, int32_t id,
- VelocityTracker::Estimator* outEstimator) {
- return mVelocityTracker.getEstimator(axis, id, outEstimator);
-}
-
// Return a strategy enum from integer value.
inline static VelocityTracker::Strategy getStrategyFromInt(const int32_t strategy) {
if (strategy < static_cast<int32_t>(VelocityTracker::Strategy::MIN) ||
@@ -135,24 +122,6 @@
return state->getVelocity(axis, id);
}
-static jboolean android_view_VelocityTracker_nativeGetEstimator(JNIEnv* env, jclass clazz,
- jlong ptr, jint axis, jint id,
- jobject outEstimatorObj) {
- VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
- VelocityTracker::Estimator estimator;
-
- bool result = state->getEstimator(axis, id, &estimator);
-
- jfloatArray coeffObj =
- jfloatArray(env->GetObjectField(outEstimatorObj, gEstimatorClassInfo.coeff));
-
- env->SetFloatArrayRegion(coeffObj, 0, VelocityTracker::Estimator::MAX_DEGREE + 1,
- estimator.coeff);
- env->SetIntField(outEstimatorObj, gEstimatorClassInfo.degree, estimator.degree);
- env->SetFloatField(outEstimatorObj, gEstimatorClassInfo.confidence, estimator.confidence);
- return result;
-}
-
static jboolean android_view_VelocityTracker_nativeIsAxisSupported(JNIEnv* env, jclass clazz,
jint axis) {
return VelocityTracker::isAxisSupported(axis);
@@ -170,23 +139,13 @@
{"nativeComputeCurrentVelocity", "(JIF)V",
(void*)android_view_VelocityTracker_nativeComputeCurrentVelocity},
{"nativeGetVelocity", "(JII)F", (void*)android_view_VelocityTracker_nativeGetVelocity},
- {"nativeGetEstimator", "(JIILandroid/view/VelocityTracker$Estimator;)Z",
- (void*)android_view_VelocityTracker_nativeGetEstimator},
{"nativeIsAxisSupported", "(I)Z",
(void*)android_view_VelocityTracker_nativeIsAxisSupported},
};
int register_android_view_VelocityTracker(JNIEnv* env) {
- int res = RegisterMethodsOrDie(env, "android/view/VelocityTracker", gVelocityTrackerMethods,
- NELEM(gVelocityTrackerMethods));
-
- jclass clazz = FindClassOrDie(env, "android/view/VelocityTracker$Estimator");
-
- gEstimatorClassInfo.coeff = GetFieldIDOrDie(env, clazz, "coeff", "[F");
- gEstimatorClassInfo.degree = GetFieldIDOrDie(env, clazz, "degree", "I");
- gEstimatorClassInfo.confidence = GetFieldIDOrDie(env, clazz, "confidence", "F");
-
- return res;
+ return RegisterMethodsOrDie(env, "android/view/VelocityTracker", gVelocityTrackerMethods,
+ NELEM(gVelocityTrackerMethods));
}
} // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8ee88ad..3f18e5a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4471,13 +4471,22 @@
<permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows an application to manage hotword detection on the device.
+ <!-- @SystemApi Allows an application to manage hotword detection and visual query detection
+ on the device.
<p>Protection level: internal|preinstalled
@hide This is not a third-party API (intended for OEMs and system apps).
-->
<permission android:name="android.permission.MANAGE_HOTWORD_DETECTION"
android:protectionLevel="internal|preinstalled" />
+ <!-- @SystemApi Must be required by a {@link android.service.voice.VisualQueryDetectionService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to subscribe to keyguard locked (i.e., showing) state.
<p>Protection level: internal|role
<p>Intended for use by ROLE_ASSISTANT only.
@@ -5326,6 +5335,14 @@
<permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
android:protectionLevel="signature|privileged|role" />
+ <!--@SystemApi Allows an application to modify system audio settings that shouldn't be
+ controllable by external apps, such as volume settings or volume behaviors for audio
+ devices, regardless of their connection status.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows an application to access the uplink and downlink audio of an ongoing
call.
<p>Not for use by third-party applications.</p>
diff --git a/core/res/OWNERS b/core/res/OWNERS
index b878189..bce500c 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -23,8 +23,8 @@
# WindowManager team
# TODO(262451702): Move WindowManager configs out of config.xml in a separate file
-per-file core/res/res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
-per-file core/res/res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
# Resources finalization
per-file res/xml/public-staging.xml = file:/tools/aapt2/OWNERS
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index c382a65..2e65800 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -93,7 +93,7 @@
android:layout_height="36dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
- style="@style/AutofillHalfSheetOutlinedButton"
+ style="?android:attr/borderlessButtonStyle"
android:text="@string/autofill_save_no">
</Button>
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index fd08241..3c0b789 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -81,7 +81,7 @@
android:layout_height="36dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
- style="@style/AutofillHalfSheetOutlinedButton"
+ style="?android:attr/borderlessButtonStyle"
android:text="@string/autofill_save_no">
</Button>
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
new file mode 100644
index 0000000..76f4171
--- /dev/null
+++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2022 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.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.text.method.OffsetMapping;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DynamicLayoutOffsetMappingTest {
+ private static final int WIDTH = 10000;
+ private static final TextPaint sTextPaint = new TextPaint();
+
+ @Test
+ public void textWithOffsetMapping() {
+ final String text = "abcde";
+ final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ final CharSequence transformedText = new TestOffsetMapping(spannable, 2, "\n");
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ assertThat(transformedText.toString()).isEqualTo("ab\ncde");
+ assertLineRange(layout, /* lineBreaks */ 0, 3, 6);
+ }
+
+ @Test
+ public void textWithOffsetMapping_deletion() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ final CharSequence transformedText =
+ new TestOffsetMapping(spannable, 3, "\n\n");
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ // delete character 'c', original text becomes "abdef"
+ spannable.delete(2, 3);
+ assertThat(transformedText.toString()).isEqualTo("ab\n\ndef");
+ assertLineRange(layout, /* lineBreaks */ 0, 3, 4, 7);
+
+ // delete character 'd', original text becomes "abef"
+ spannable.delete(2, 3);
+ assertThat(transformedText.toString()).isEqualTo("ab\n\nef");
+ assertLineRange(layout, /* lineBreaks */ 0, 3, 4, 6);
+
+ // delete "be", original text becomes "af"
+ spannable.delete(1, 3);
+ assertThat(transformedText.toString()).isEqualTo("a\n\nf");
+ assertLineRange(layout, /* lineBreaks */ 0, 2, 3, 4);
+ }
+
+ @Test
+ public void textWithOffsetMapping_insertion() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ spannable.insert(3, "x");
+ assertThat(transformedText.toString()).isEqualTo("abcx\n\ndef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 9);
+
+ spannable.insert(5, "x");
+ assertThat(transformedText.toString()).isEqualTo("abcx\n\ndxef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 10);
+ }
+
+ @Test
+ public void textWithOffsetMapping_replace() {
+ final String text = "abcdef";
+ final SpannableStringBuilder spannable = new SpannableStringBuilder(text);
+ final CharSequence transformedText = new TestOffsetMapping(spannable, 3, "\n\n");
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(spannable, sTextPaint, WIDTH)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(false)
+ .setDisplayText(transformedText)
+ .build();
+
+ spannable.replace(2, 4, "xx");
+ assertThat(transformedText.toString()).isEqualTo("abxx\n\nef");
+ assertLineRange(layout, /* lineBreaks */ 0, 5, 6, 8);
+ }
+
+ private void assertLineRange(Layout layout, int... lineBreaks) {
+ final int lineCount = lineBreaks.length - 1;
+ assertThat(layout.getLineCount()).isEqualTo(lineCount);
+ for (int line = 0; line < lineCount; ++line) {
+ assertThat(layout.getLineStart(line)).isEqualTo(lineBreaks[line]);
+ }
+ assertThat(layout.getLineEnd(lineCount - 1)).isEqualTo(lineBreaks[lineCount]);
+ }
+
+ /**
+ * A test TransformedText that inserts some text at the given offset.
+ */
+ private static class TestOffsetMapping implements OffsetMapping, CharSequence {
+ private final int mOriginalInsertOffset;
+ private final CharSequence mOriginal;
+ private final CharSequence mInsertText;
+ TestOffsetMapping(CharSequence original, int insertOffset,
+ CharSequence insertText) {
+ mOriginal = original;
+ if (mOriginal instanceof Spannable) {
+ ((Spannable) mOriginal).setSpan(INSERT_POINT, insertOffset, insertOffset,
+ Spanned.SPAN_POINT_POINT);
+ }
+ mOriginalInsertOffset = insertOffset;
+ mInsertText = insertText;
+ }
+
+ private int getInsertOffset() {
+ if (mOriginal instanceof Spannable) {
+ return ((Spannable) mOriginal).getSpanStart(INSERT_POINT);
+ }
+ return mOriginalInsertOffset;
+ }
+
+ @Override
+ public int originalToTransformed(int offset, int strategy) {
+ final int insertOffset = getInsertOffset();
+ if (strategy == OffsetMapping.MAP_STRATEGY_CURSOR && offset == insertOffset) {
+ return offset;
+ }
+ if (offset < getInsertOffset()) {
+ return offset;
+ }
+ return offset + mInsertText.length();
+ }
+
+ @Override
+ public int transformedToOriginal(int offset, int strategy) {
+ final int insertOffset = getInsertOffset();
+ if (offset < insertOffset) {
+ return offset;
+ }
+ if (offset < insertOffset + mInsertText.length()) {
+ return insertOffset;
+ }
+ return offset - mInsertText.length();
+ }
+
+ @Override
+ public void originalToTransformed(TextUpdate textUpdate) {
+ final int insertOffset = getInsertOffset();
+ if (textUpdate.where <= insertOffset) {
+ if (textUpdate.where + textUpdate.before > insertOffset) {
+ textUpdate.before += mInsertText.length();
+ textUpdate.after += mInsertText.length();
+ }
+ } else {
+ textUpdate.where += mInsertText.length();
+ }
+ }
+
+ @Override
+ public int length() {
+ return mOriginal.length() + mInsertText.length();
+ }
+
+ @Override
+ public char charAt(int index) {
+ final int insertOffset = getInsertOffset();
+ if (index < insertOffset) {
+ return mOriginal.charAt(index);
+ }
+ if (index < insertOffset + mInsertText.length()) {
+ return mInsertText.charAt(index - insertOffset);
+ }
+ return mOriginal.charAt(index - mInsertText.length());
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int index = start; index < end; ++index) {
+ stringBuilder.append(charAt(index));
+ }
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int index = 0; index < length(); ++index) {
+ stringBuilder.append(charAt(index));
+ }
+ return stringBuilder.toString();
+ }
+
+ static final NoCopySpan INSERT_POINT = new NoCopySpan() { };
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index cc4fbab..f5582d0 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -19,19 +19,30 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Paint;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.text.GetChars;
import android.text.Layout;
import android.text.PrecomputedText;
+import android.text.method.OffsetMapping;
+import android.text.method.TransformationMethod;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView.BufferType;
@@ -321,6 +332,94 @@
assertEquals("hi", charWrapper.toString());
}
+ @Test
+ @UiThreadTest
+ public void transformedToOriginal_noOffsetMapping() {
+ mTextView = new TextView(mActivity);
+ final String text = "Hello world";
+ mTextView.setText(text);
+ for (int offset = 0; offset < text.length(); ++offset) {
+ assertThat(mTextView.transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR))
+ .isEqualTo(offset);
+ assertThat(mTextView.transformedToOriginal(offset,
+ OffsetMapping.MAP_STRATEGY_CHARACTER)).isEqualTo(offset);
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ public void originalToTransformed_noOffsetMapping() {
+ mTextView = new TextView(mActivity);
+ final String text = "Hello world";
+ mTextView.setText(text);
+ for (int offset = 0; offset < text.length(); ++offset) {
+ assertThat(mTextView.originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR))
+ .isEqualTo(offset);
+ assertThat(mTextView.originalToTransformed(offset,
+ OffsetMapping.MAP_STRATEGY_CHARACTER)).isEqualTo(offset);
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ public void originalToTransformed_hasOffsetMapping() {
+ mTextView = new TextView(mActivity);
+ final CharSequence text = "Hello world";
+ final TransformedText transformedText = mock(TransformedText.class);
+ when(transformedText.originalToTransformed(anyInt(), anyInt())).then((invocation) -> {
+ // plus 1 for character strategy and minus 1 for cursor strategy.
+ if ((int) invocation.getArgument(1) == OffsetMapping.MAP_STRATEGY_CHARACTER) {
+ return (int) invocation.getArgument(0) + 1;
+ }
+ return (int) invocation.getArgument(0) - 1;
+ });
+
+ final TransformationMethod transformationMethod =
+ new TestTransformationMethod(transformedText);
+ mTextView.setText(text);
+ mTextView.setTransformationMethod(transformationMethod);
+
+ assertThat(mTextView.originalToTransformed(1, OffsetMapping.MAP_STRATEGY_CHARACTER))
+ .isEqualTo(2);
+ verify(transformedText, times(1))
+ .originalToTransformed(1, OffsetMapping.MAP_STRATEGY_CHARACTER);
+
+ assertThat(mTextView.originalToTransformed(1, OffsetMapping.MAP_STRATEGY_CURSOR))
+ .isEqualTo(0);
+ verify(transformedText, times(1))
+ .originalToTransformed(1, OffsetMapping.MAP_STRATEGY_CURSOR);
+ }
+
+ @Test
+ @UiThreadTest
+ public void transformedToOriginal_hasOffsetMapping() {
+ mTextView = new TextView(mActivity);
+ final CharSequence text = "Hello world";
+ final TransformedText transformedText = mock(TransformedText.class);
+ when(transformedText.transformedToOriginal(anyInt(), anyInt())).then((invocation) -> {
+ // plus 1 for character strategy and minus 1 for cursor strategy.
+ if ((int) invocation.getArgument(1) == OffsetMapping.MAP_STRATEGY_CHARACTER) {
+ return (int) invocation.getArgument(0) + 1;
+ }
+ return (int) invocation.getArgument(0) - 1;
+ });
+
+ final TransformationMethod transformationMethod =
+ new TestTransformationMethod(transformedText);
+ mTextView.setText(text);
+ mTextView.setTransformationMethod(transformationMethod);
+
+ assertThat(mTextView.transformedToOriginal(1, OffsetMapping.MAP_STRATEGY_CHARACTER))
+ .isEqualTo(2);
+ verify(transformedText, times(1))
+ .transformedToOriginal(1, OffsetMapping.MAP_STRATEGY_CHARACTER);
+
+ assertThat(mTextView.transformedToOriginal(1, OffsetMapping.MAP_STRATEGY_CURSOR))
+ .isEqualTo(0);
+ verify(transformedText, times(1))
+ .transformedToOriginal(1, OffsetMapping.MAP_STRATEGY_CURSOR);
+ }
+
private String createLongText() {
int size = 600 * 1000;
final StringBuilder builder = new StringBuilder(size);
@@ -351,4 +450,24 @@
}
}
}
+
+ private interface TransformedText extends OffsetMapping, CharSequence { }
+
+ private static class TestTransformationMethod implements TransformationMethod {
+ private final CharSequence mTransformedText;
+
+ TestTransformationMethod(CharSequence transformedText) {
+ this.mTransformedText = transformedText;
+ }
+
+ @Override
+ public CharSequence getTransformation(CharSequence source, View view) {
+ return mTransformedText;
+ }
+
+ @Override
+ public void onFocusChanged(View view, CharSequence sourceText, boolean focused,
+ int direction, Rect previouslyFocusedRect) {
+ }
+ }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5040a86..bf7f3bd 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -454,6 +454,7 @@
<!-- Permission required for hotword detection service CTS tests -->
<permission name="android.permission.MANAGE_HOTWORD_DETECTION" />
<permission name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
+ <permission name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" />
<permission name="android.permission.MANAGE_APP_HIBERNATION"/>
<!-- Permission required for CTS test - ResourceObserverNativeTest -->
<permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
@@ -464,6 +465,7 @@
<!-- Permission required for CTS test - SystemMediaRouter2Test -->
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
+ <permission name="android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS"/>
<!-- Permission required for CTS test - CallAudioInterceptionTest -->
<permission name="android.permission.CALL_AUDIO_INTERCEPTION"/>
<!-- Permission required for CTS test - CtsPermission5TestCases -->
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 07d0158..c5d932e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -79,6 +79,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.core.util.function.Function;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
@@ -563,10 +564,10 @@
SplitAttributes.SplitType.RatioSplitType.splitEqually()
)
).build();
+ final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
+ params -> splitAttributes;
- mController.setSplitAttributesCalculator(params -> {
- return splitAttributes;
- });
+ mController.setSplitAttributesCalculator(calculator);
assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
splitPairRule, null /* minDimensionsPair */));
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 5de5365..cddbf469 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 8121b20..e85b3c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -232,10 +232,13 @@
if (mBubble.isAppBubble()) {
PendingIntent pi = PendingIntent.getActivity(mContext, 0,
- mBubble.getAppBubbleIntent(),
- PendingIntent.FLAG_MUTABLE,
+ mBubble.getAppBubbleIntent()
+ .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+ PendingIntent.FLAG_IMMUTABLE,
null);
- mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+ mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
+ launchBounds);
} else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
options.setApplyActivityFlagsForBubbles(true);
mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index d0aef20..9edfffc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -384,7 +384,7 @@
@Nullable ImeTracker.Token statsToken) {
final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
if (imeSource == null || mImeSourceControl == null) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
final Rect newFrame = imeSource.getFrame();
@@ -407,7 +407,8 @@
}
if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
|| (mAnimationDirection == DIRECTION_HIDE && !show)) {
- ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+ ImeTracker.forLogging().onCancelled(
+ statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
boolean seek = false;
@@ -451,7 +452,7 @@
mTransactionPool.release(t);
});
mAnimation.setInterpolator(INTERPOLATOR);
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
mAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled = false;
@Nullable
@@ -474,7 +475,7 @@
: 1.f;
t.setAlpha(mImeSourceControl.getLeash(), alpha);
if (mAnimationDirection == DIRECTION_SHOW) {
- ImeTracker.get().onProgress(mStatsToken,
+ ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.show(mImeSourceControl.getLeash());
}
@@ -511,15 +512,15 @@
}
dispatchEndPositioning(mDisplayId, mCancelled, t);
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
- ImeTracker.get().onProgress(mStatsToken,
+ ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.hide(mImeSourceControl.getLeash());
removeImeSurface();
- ImeTracker.get().onHidden(mStatsToken);
+ ImeTracker.forLogging().onHidden(mStatsToken);
} else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
- ImeTracker.get().onShown(mStatsToken);
+ ImeTracker.forLogging().onShown(mStatsToken);
} else if (mCancelled) {
- ImeTracker.get().onCancelled(mStatsToken,
+ ImeTracker.forLogging().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
}
if (DEBUG_IME_VISIBILITY) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 8759301..9bdda14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -162,10 +162,12 @@
@Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+ ImeTracker.forLogging().onProgress(
+ statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
listener.showInsets(types, fromIme, statsToken);
}
@@ -175,10 +177,12 @@
@Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+ ImeTracker.forLogging().onProgress(
+ statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
listener.hideInsets(types, fromIme, statsToken);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index fcbf9e0..fa3a6ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -78,6 +78,7 @@
private final Rect mResizingBounds = new Rect();
private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
+ private ValueAnimator mScreenshotAnimator;
private int mIconSize;
private int mOffsetX;
@@ -135,8 +136,17 @@
/** Releases the surfaces for split decor. */
public void release(SurfaceControl.Transaction t) {
- if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
- mFadeAnimator.cancel();
+ if (mFadeAnimator != null) {
+ if (mFadeAnimator.isRunning()) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+ }
+ if (mScreenshotAnimator != null) {
+ if (mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+ mScreenshotAnimator = null;
}
if (mViewHost != null) {
mViewHost.release();
@@ -238,16 +248,20 @@
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
if (mScreenshot != null) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
t.setPosition(mScreenshot, mOffsetX, mOffsetY);
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
- final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
- va.addUpdateListener(valueAnimator -> {
+ mScreenshotAnimator = ValueAnimator.ofFloat(1, 0);
+ mScreenshotAnimator.addUpdateListener(valueAnimator -> {
final float progress = (float) valueAnimator.getAnimatedValue();
animT.setAlpha(mScreenshot, progress);
animT.apply();
});
- va.addListener(new AnimatorListenerAdapter() {
+ mScreenshotAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mRunningAnimationCount++;
@@ -266,7 +280,7 @@
}
}
});
- va.start();
+ mScreenshotAnimator.start();
}
if (mResizingIconView == null) {
@@ -292,9 +306,6 @@
});
return;
}
-
- // If fade-in animation is running, cancel it and re-run fade-out one.
- mFadeAnimator.cancel();
}
if (mShown) {
fadeOutDecor(animFinishedCallback);
@@ -332,6 +343,11 @@
* directly. */
public void fadeOutDecor(Runnable finishedCallback) {
if (mShown) {
+ // If previous animation is running, just cancel it.
+ if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ mFadeAnimator.cancel();
+ }
+
startFadeAnimation(false /* show */, true, finishedCallback);
mShown = false;
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index b189163..8d4a384 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -125,7 +125,9 @@
cancelScheduledPlacement();
applyPlacement(placement, shouldStash, animationDuration);
} else {
- applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+ if (mCurrentPlacementBounds != null) {
+ applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+ }
schedulePinnedStackPlacement(placement, animationDuration);
}
}
@@ -188,7 +190,7 @@
applyPlacementBounds(bounds, animationDuration);
}
- void onPipDismissed() {
+ void reset() {
mCurrentPlacementBounds = null;
mPipTargetBounds = null;
cancelScheduledPlacement();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index fd4fcff..4426423 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -450,18 +450,6 @@
mPipMediaController.registerSessionListenerForCurrentUser();
}
- private void checkIfPinnedTaskAppeared() {
- final TaskInfo pinnedTask = getPinnedTaskInfo();
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: checkIfPinnedTaskAppeared(), task=%s", TAG, pinnedTask);
- if (pinnedTask == null || pinnedTask.topActivity == null) return;
- mPinnedTaskId = pinnedTask.taskId;
-
- mPipMediaController.onActivityPinned();
- mActionBroadcastReceiver.register();
- mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
- }
-
private void checkIfPinnedTaskIsGone() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onTaskStackChanged()", TAG);
@@ -482,7 +470,7 @@
mTvPipMenuController.closeMenu();
mTvPipBoundsState.resetTvPipState();
- mTvPipBoundsController.onPipDismissed();
+ mTvPipBoundsController.reset();
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}
@@ -537,7 +525,16 @@
taskStackListener.addListener(new TaskStackListenerCallback() {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- checkIfPinnedTaskAppeared();
+ final TaskInfo pinnedTask = getPinnedTaskInfo();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onActivityPinned(), task=%s", TAG, pinnedTask);
+ if (pinnedTask == null || pinnedTask.topActivity == null) return;
+ mPinnedTaskId = pinnedTask.taskId;
+
+ mPipMediaController.onActivityPinned();
+ mActionBroadcastReceiver.register();
+ mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
+ mTvPipBoundsController.reset();
mAppOpsListener.onActivityPinned(packageName);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 1488469..a6c4ac2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1072,6 +1072,7 @@
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
@@ -1103,6 +1104,7 @@
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
+ finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
@@ -1485,6 +1487,12 @@
}
private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+ // If split didn't active, just ignore this callback because we should already did these
+ // on #applyExitSplitScreen.
+ if (!isSplitActive()) {
+ return;
+ }
+
final boolean sideStageVisible = mSideStageListener.mVisible;
final boolean mainStageVisible = mMainStageListener.mVisible;
@@ -1493,20 +1501,23 @@
return;
}
+ // Check if it needs to dismiss split screen when both stage invisible.
+ if (!mainStageVisible && mExitSplitScreenOnHide) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
+ return;
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (!mainStageVisible) {
+ // Split entering background.
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
wct.setForceTranslucent(mRootTaskInfo.token, true);
- // Both stages are not visible, check if it needs to dismiss split screen.
- if (mExitSplitScreenOnHide) {
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
- }
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
}
+
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
setDividerVisibility(mainStageVisible, t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
index 5c45527..04ae2f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
@@ -24,12 +24,12 @@
/**
* Algorithm for determining the type of a new starting window on Android TV.
- * For now we always show empty splash screens on Android TV.
+ * For now we do not want to show any splash screens on Android TV.
*/
public class TvStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorithm {
@Override
public int getSuggestedWindowType(StartingWindowInfo windowInfo) {
- // For now we want to always show empty splash screens on TV.
+ // For now we do not want to show any splash screens on TV.
return STARTING_WINDOW_TYPE_NONE;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 1e72c56..129924a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -136,7 +136,7 @@
}
private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
- int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+ final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
decoration.setCaptionColor(statusBarColor);
}
@@ -152,7 +152,7 @@
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ final CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (oldDecoration != null) {
// close the old decoration if it exists to avoid two window decorations being added
oldDecoration.close();
@@ -169,9 +169,9 @@
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- TaskPositioner taskPositioner =
+ final TaskPositioner taskPositioner =
new TaskPositioner(mTaskOrganizer, windowDecoration);
- CaptionTouchEventListener touchEventListener =
+ final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
@@ -221,11 +221,11 @@
if (e.getAction() != MotionEvent.ACTION_DOWN) {
return false;
}
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.isFocused) {
return false;
}
- WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mTaskToken, true /* onTop */);
mSyncQueue.queue(wct);
return true;
@@ -236,7 +236,7 @@
* @return {@code true} if a drag is happening; or {@code false} if it is not
*/
private void handleEventForMove(MotionEvent e) {
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 8609c6b..d26f1fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -48,15 +48,13 @@
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
private DragResizeCallback mDragResizeCallback;
-
private DragResizeInputListener mDragResizeListener;
+ private final DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final RelayoutResult<WindowDecorLinearLayout> mResult =
new RelayoutResult<>();
- private DragDetector mDragDetector;
-
CaptionWindowDecoration(
Context context,
DisplayController displayController,
@@ -104,14 +102,14 @@
taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
- WindowDecorLinearLayout oldRootView = mResult.mRootView;
+ final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- int outsetLeftId = R.dimen.freeform_resize_handle;
- int outsetTopId = R.dimen.freeform_resize_handle;
- int outsetRightId = R.dimen.freeform_resize_handle;
- int outsetBottomId = R.dimen.freeform_resize_handle;
+ final int outsetLeftId = R.dimen.freeform_resize_handle;
+ final int outsetTopId = R.dimen.freeform_resize_handle;
+ final int outsetRightId = R.dimen.freeform_resize_handle;
+ final int outsetBottomId = R.dimen.freeform_resize_handle;
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
@@ -123,7 +121,7 @@
}
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
- taskInfo = null; // Clear it just in case we use it accidentally
+ // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
mTaskOrganizer.applyTransaction(wct);
@@ -152,12 +150,13 @@
mDragResizeCallback);
}
- int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+ final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
+ .getScaledTouchSlop();
mDragDetector.setTouchSlop(touchSlop);
- int resize_handle = mResult.mRootView.getResources()
+ final int resize_handle = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_handle);
- int resize_corner = mResult.mRootView.getResources()
+ final int resize_corner = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_corner);
mDragResizeListener.setGeometry(
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
@@ -167,15 +166,15 @@
* Sets up listeners when a new root view is created.
*/
private void setupRootView() {
- View caption = mResult.mRootView.findViewById(R.id.caption);
+ final View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
- View close = caption.findViewById(R.id.close_window);
+ final View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
- View back = caption.findViewById(R.id.back_button);
+ final View back = caption.findViewById(R.id.back_button);
back.setOnClickListener(mOnCaptionButtonClickListener);
- View minimize = caption.findViewById(R.id.minimize_window);
+ final View minimize = caption.findViewById(R.id.minimize_window);
minimize.setOnClickListener(mOnCaptionButtonClickListener);
- View maximize = caption.findViewById(R.id.maximize_window);
+ final View maximize = caption.findViewById(R.id.maximize_window);
maximize.setOnClickListener(mOnCaptionButtonClickListener);
}
@@ -184,31 +183,31 @@
return;
}
- View caption = mResult.mRootView.findViewById(R.id.caption);
- GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
+ final View caption = mResult.mRootView.findViewById(R.id.caption);
+ final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
captionDrawable.setColor(captionColor);
- int buttonTintColorRes =
+ final int buttonTintColorRes =
Color.valueOf(captionColor).luminance() < 0.5
? R.color.decor_button_light_color
: R.color.decor_button_dark_color;
- ColorStateList buttonTintColor =
+ final ColorStateList buttonTintColor =
caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
- View back = caption.findViewById(R.id.back_button);
- VectorDrawable backBackground = (VectorDrawable) back.getBackground();
+ final View back = caption.findViewById(R.id.back_button);
+ final VectorDrawable backBackground = (VectorDrawable) back.getBackground();
backBackground.setTintList(buttonTintColor);
- View minimize = caption.findViewById(R.id.minimize_window);
- VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground();
+ final View minimize = caption.findViewById(R.id.minimize_window);
+ final VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground();
minimizeBackground.setTintList(buttonTintColor);
- View maximize = caption.findViewById(R.id.maximize_window);
- VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
+ final View maximize = caption.findViewById(R.id.maximize_window);
+ final VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
maximizeBackground.setTintList(buttonTintColor);
- View close = caption.findViewById(R.id.close_window);
- VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
+ final View close = caption.findViewById(R.id.close_window);
+ final VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
closeBackground.setTintList(buttonTintColor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 13b4a95..2863adc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -68,9 +68,8 @@
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
- private FreeformTaskTransitionStarter mTransitionStarter;
- private Optional<DesktopModeController> mDesktopModeController;
- private Optional<DesktopTasksController> mDesktopTasksController;
+ private final Optional<DesktopModeController> mDesktopModeController;
+ private final Optional<DesktopTasksController> mDesktopTasksController;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -78,7 +77,7 @@
private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
- private InputMonitorFactory mInputMonitorFactory;
+ private final InputMonitorFactory mInputMonitorFactory;
private TaskOperations mTaskOperations;
public DesktopModeWindowDecorViewModel(
@@ -199,7 +198,7 @@
if (decoration == null) return;
decoration.close();
- int displayId = taskInfo.displayId;
+ final int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)) {
removeTaskFromEventReceiver(displayId);
}
@@ -227,7 +226,7 @@
@Override
public void onClick(View v) {
- DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
mTaskOperations.closeTask(mTaskToken);
@@ -250,7 +249,7 @@
@Override
public boolean onTouch(View v, MotionEvent e) {
boolean isDrag = false;
- int id = v.getId();
+ final int id = v.getId();
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
return false;
}
@@ -261,11 +260,11 @@
if (e.getAction() != MotionEvent.ACTION_DOWN) {
return isDrag;
}
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.isFocused) {
return isDrag;
}
- WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mTaskToken, true /* onTop */);
mSyncQueue.queue(wct);
return true;
@@ -276,7 +275,7 @@
* @return {@code true} if a drag is happening; or {@code false} if it is not
*/
private void handleEventForMove(MotionEvent e) {
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return;
@@ -295,16 +294,16 @@
break;
}
case MotionEvent.ACTION_MOVE: {
- int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragResizeCallback.onDragResizeMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
- .stableInsets().top;
+ final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ final int statusBarHeight = mDisplayController
+ .getDisplayLayout(taskInfo.displayId).stableInsets().top;
mDragResizeCallback.onDragResizeEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
@@ -378,7 +377,7 @@
*/
private void incrementEventReceiverTasks(int displayId) {
if (mEventReceiversByDisplay.contains(displayId)) {
- EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
eventReceiver.incrementTaskNumber();
} else {
createInputChannel(displayId);
@@ -388,7 +387,7 @@
// If all tasks on this display are gone, we don't need to monitor its input.
private void removeTaskFromEventReceiver(int displayId) {
if (!mEventReceiversByDisplay.contains(displayId)) return;
- EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
if (eventReceiver == null) return;
eventReceiver.decrementTaskNumber();
if (eventReceiver.getTasksOnDisplay() == 0) {
@@ -403,7 +402,7 @@
*/
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
if (DesktopModeStatus.isProto2Enabled()) {
- DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor == null
|| focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
handleCaptionThroughStatusBar(ev);
@@ -428,9 +427,9 @@
// If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
private void handleEventOutsideFocusedCaption(MotionEvent ev) {
- int action = ev.getActionMasked();
+ final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor == null) {
return;
}
@@ -450,7 +449,7 @@
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
// Begin drag through status bar if applicable.
- DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor != null) {
boolean dragFromStatusBarAllowed = false;
if (DesktopModeStatus.isProto2Enabled()) {
@@ -469,14 +468,14 @@
break;
}
case MotionEvent.ACTION_UP: {
- DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor == null) {
mTransitionDragActive = false;
return;
}
if (mTransitionDragActive) {
mTransitionDragActive = false;
- int statusBarHeight = mDisplayController
+ final int statusBarHeight = mDisplayController
.getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
if (ev.getY() > statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
@@ -500,10 +499,10 @@
@Nullable
private DesktopModeWindowDecoration getFocusedDecor() {
- int size = mWindowDecorByTaskId.size();
+ final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
for (int i = 0; i < size; i++) {
- DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
break;
@@ -513,16 +512,16 @@
}
private void createInputChannel(int displayId) {
- InputManager inputManager = InputManager.getInstance();
- InputMonitor inputMonitor =
+ final InputManager inputManager = InputManager.getInstance();
+ final InputMonitor inputMonitor =
mInputMonitorFactory.create(inputManager, mContext);
- EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+ final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver);
}
private void disposeInputChannel(int displayId) {
- EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+ final EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
if (eventReceiver != null) {
eventReceiver.dispose();
}
@@ -541,7 +540,7 @@
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ final DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (oldDecoration != null) {
// close the old decoration if it exists to avoid two window decorations being added
oldDecoration.close();
@@ -558,9 +557,9 @@
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- TaskPositioner taskPositioner =
+ final TaskPositioner taskPositioner =
new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
- DesktopModeTouchEventListener touchEventListener =
+ final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(
taskInfo, taskPositioner, windowDecoration.getDragDetector());
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 9c2beb9..1a38d24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -56,17 +56,14 @@
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
private DragResizeCallback mDragResizeCallback;
-
private DragResizeInputListener mDragResizeListener;
+ private final DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
private boolean mDesktopActive;
-
- private DragDetector mDragDetector;
-
private AdditionalWindow mHandleMenu;
DesktopModeWindowDecoration(
@@ -121,14 +118,14 @@
taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
- WindowDecorLinearLayout oldRootView = mResult.mRootView;
+ final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- int outsetLeftId = R.dimen.freeform_resize_handle;
- int outsetTopId = R.dimen.freeform_resize_handle;
- int outsetRightId = R.dimen.freeform_resize_handle;
- int outsetBottomId = R.dimen.freeform_resize_handle;
+ final int outsetLeftId = R.dimen.freeform_resize_handle;
+ final int outsetTopId = R.dimen.freeform_resize_handle;
+ final int outsetRightId = R.dimen.freeform_resize_handle;
+ final int outsetBottomId = R.dimen.freeform_resize_handle;
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
@@ -152,7 +149,7 @@
mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
- taskInfo = null; // Clear it just in case we use it accidentally
+ // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
mTaskOrganizer.applyTransaction(wct);
@@ -197,12 +194,13 @@
mDragResizeCallback);
}
- int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+ final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
+ .getScaledTouchSlop();
mDragDetector.setTouchSlop(touchSlop);
- int resize_handle = mResult.mRootView.getResources()
+ final int resize_handle = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_handle);
- int resize_corner = mResult.mRootView.getResources()
+ final int resize_corner = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_corner);
mDragResizeListener.setGeometry(
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
@@ -212,27 +210,27 @@
* Sets up listeners when a new root view is created.
*/
private void setupRootView() {
- View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
- View close = caption.findViewById(R.id.close_window);
+ final View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
- View back = caption.findViewById(R.id.back_button);
+ final View back = caption.findViewById(R.id.back_button);
back.setOnClickListener(mOnCaptionButtonClickListener);
- View handle = caption.findViewById(R.id.caption_handle);
+ final View handle = caption.findViewById(R.id.caption_handle);
handle.setOnTouchListener(mOnCaptionTouchListener);
handle.setOnClickListener(mOnCaptionButtonClickListener);
updateButtonVisibility();
}
private void setupHandleMenu() {
- View menu = mHandleMenu.mWindowViewHost.getView();
- View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ final View menu = mHandleMenu.mWindowViewHost.getView();
+ final View fullscreen = menu.findViewById(R.id.fullscreen_button);
fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
- View desktop = menu.findViewById(R.id.desktop_button);
+ final View desktop = menu.findViewById(R.id.desktop_button);
desktop.setOnClickListener(mOnCaptionButtonClickListener);
- View split = menu.findViewById(R.id.split_screen_button);
+ final View split = menu.findViewById(R.id.split_screen_button);
split.setOnClickListener(mOnCaptionButtonClickListener);
- View more = menu.findViewById(R.id.more_button);
+ final View more = menu.findViewById(R.id.more_button);
more.setOnClickListener(mOnCaptionButtonClickListener);
}
@@ -242,8 +240,8 @@
* @param visible whether or not the caption should be visible
*/
private void setCaptionVisibility(boolean visible) {
- int v = visible ? View.VISIBLE : View.GONE;
- View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ final int v = visible ? View.VISIBLE : View.GONE;
+ final View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
captionView.setVisibility(v);
if (!visible) closeHandleMenu();
}
@@ -264,19 +262,19 @@
* Show or hide buttons
*/
void setButtonVisibility(boolean visible) {
- int visibility = visible ? View.VISIBLE : View.GONE;
- View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- View back = caption.findViewById(R.id.back_button);
- View close = caption.findViewById(R.id.close_window);
+ final int visibility = visible ? View.VISIBLE : View.GONE;
+ final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ final View back = caption.findViewById(R.id.back_button);
+ final View close = caption.findViewById(R.id.close_window);
back.setVisibility(visibility);
close.setVisibility(visibility);
- int buttonTintColorRes =
+ final int buttonTintColorRes =
mDesktopActive ? R.color.decor_button_dark_color
: R.color.decor_button_light_color;
- ColorStateList buttonTintColor =
+ final ColorStateList buttonTintColor =
caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
- View handle = caption.findViewById(R.id.caption_handle);
- VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
+ final View handle = caption.findViewById(R.id.caption_handle);
+ final VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
handleBackground.setTintList(buttonTintColor);
caption.getBackground().setTint(visible ? Color.WHITE : Color.TRANSPARENT);
}
@@ -297,12 +295,12 @@
* Create and display handle menu window
*/
void createHandleMenu() {
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mDecorWindowContext.getResources();
- int x = mRelayoutParams.mCaptionX;
- int y = mRelayoutParams.mCaptionY;
- int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
- int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ final int x = mRelayoutParams.mCaptionX;
+ final int y = mRelayoutParams.mCaptionY;
+ final int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ final int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String namePrefix = "Caption Menu";
mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t,
x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
@@ -353,8 +351,8 @@
* @return the point of the input in local space
*/
private PointF offsetCaptionLocation(MotionEvent ev) {
- PointF result = new PointF(ev.getX(), ev.getY());
- Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+ final PointF result = new PointF(ev.getX(), ev.getY());
+ final Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
.positionInParent;
result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
result.offset(-positionInParent.x, -positionInParent.y);
@@ -370,8 +368,8 @@
*/
private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
if (mResult.mRootView == null) return false;
- PointF inputPoint = offsetCaptionLocation(ev);
- View view = mResult.mRootView.findViewById(layoutId);
+ final PointF inputPoint = offsetCaptionLocation(ev);
+ final View view = mResult.mRootView.findViewById(layoutId);
return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
}
@@ -389,20 +387,20 @@
*/
void checkClickEvent(MotionEvent ev) {
if (mResult.mRootView == null) return;
- View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- PointF inputPoint = offsetCaptionLocation(ev);
+ final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ final PointF inputPoint = offsetCaptionLocation(ev);
if (!isHandleMenuActive()) {
- View handle = caption.findViewById(R.id.caption_handle);
+ final View handle = caption.findViewById(R.id.caption_handle);
clickIfPointInView(inputPoint, handle);
} else {
- View menu = mHandleMenu.mWindowViewHost.getView();
- View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ final View menu = mHandleMenu.mWindowViewHost.getView();
+ final View fullscreen = menu.findViewById(R.id.fullscreen_button);
if (clickIfPointInView(inputPoint, fullscreen)) return;
- View desktop = menu.findViewById(R.id.desktop_button);
+ final View desktop = menu.findViewById(R.id.desktop_button);
if (clickIfPointInView(inputPoint, desktop)) return;
- View split = menu.findViewById(R.id.split_screen_button);
+ final View split = menu.findViewById(R.id.split_screen_button);
if (clickIfPointInView(inputPoint, split)) return;
- View more = menu.findViewById(R.id.more_button);
+ final View more = menu.findViewById(R.id.more_button);
clickIfPointInView(inputPoint, more);
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 27fc381a..65923ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -22,6 +22,10 @@
<!-- Ensure output directory is empty at the start -->
<option name="run-command" value="rm -rf /sdcard/flicker" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
+ <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+ </target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="WMShellFlickerTests.apk"/>
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index 4e2ce91..77fa9dc 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -16,6 +16,7 @@
package android.media;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -318,8 +319,10 @@
* @param ada the device for which volume is to be modified
*/
@SystemApi
- // TODO alternatively require MODIFY_AUDIO_SYSTEM_SETTINGS when defined
- @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS
+ })
public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
try {
getService().setDeviceVolume(vi, ada, mPackageName);
@@ -340,8 +343,10 @@
* @param ada the device for which volume is to be retrieved
*/
@SystemApi
- // TODO alternatively require MODIFY_AUDIO_SYSTEM_SETTINGS when defined
- @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS
+ })
public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
@NonNull AudioDeviceAttributes ada) {
try {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5ee32d6..c06352c 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -106,9 +106,11 @@
void setStreamVolumeWithAttribution(int streamType, int index, int flags,
in String callingPackage, in String attributionTag);
+ @EnforcePermission(anyOf = {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SYSTEM_SETTINGS"})
void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
in String callingPackage);
+ @EnforcePermission(anyOf = {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SYSTEM_SETTINGS"})
VolumeInfo getDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
in String callingPackage);
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 98819a3..0198419 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -28,11 +28,11 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* @hide
@@ -140,7 +140,7 @@
*/
public static int getLegacyStreamTypeForStrategyWithAudioAttributes(
@NonNull AudioAttributes audioAttributes) {
- Preconditions.checkNotNull(audioAttributes, "AudioAttributes must not be null");
+ Objects.requireNonNull(audioAttributes, "AudioAttributes must not be null");
for (final AudioProductStrategy productStrategy :
AudioProductStrategy.getAudioProductStrategies()) {
if (productStrategy.supportsAudioAttributes(audioAttributes)) {
@@ -160,6 +160,30 @@
return AudioSystem.STREAM_MUSIC;
}
+ /**
+ * @hide
+ * @param attributes the {@link AudioAttributes} to identify VolumeGroupId with
+ * @param fallbackOnDefault if set, allows to fallback on the default group (e.g. the group
+ * associated to {@link AudioManager#STREAM_MUSIC}).
+ * @return volume group id associated with the given {@link AudioAttributes} if found,
+ * default volume group id if fallbackOnDefault is set
+ * <p>By convention, the product strategy with default attributes will be associated to the
+ * default volume group (e.g. associated to {@link AudioManager#STREAM_MUSIC})
+ * or {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} if not found.
+ */
+ public static int getVolumeGroupIdForAudioAttributes(
+ @NonNull AudioAttributes attributes, boolean fallbackOnDefault) {
+ Objects.requireNonNull(attributes, "attributes must not be null");
+ int volumeGroupId = getVolumeGroupIdForAudioAttributesInt(attributes);
+ if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
+ return volumeGroupId;
+ }
+ if (fallbackOnDefault) {
+ return getVolumeGroupIdForAudioAttributesInt(getDefaultAttributes());
+ }
+ return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+ }
+
private static List<AudioProductStrategy> initializeAudioProductStrategies() {
ArrayList<AudioProductStrategy> apsList = new ArrayList<AudioProductStrategy>();
int status = native_list_audio_product_strategies(apsList);
@@ -190,8 +214,8 @@
*/
private AudioProductStrategy(@NonNull String name, int id,
@NonNull AudioAttributesGroup[] aag) {
- Preconditions.checkNotNull(name, "name must not be null");
- Preconditions.checkNotNull(aag, "AudioAttributesGroups must not be null");
+ Objects.requireNonNull(name, "name must not be null");
+ Objects.requireNonNull(aag, "AudioAttributesGroups must not be null");
mName = name;
mId = id;
mAudioAttributesGroups = aag;
@@ -241,7 +265,7 @@
*/
@TestApi
public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) {
- Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+ Objects.requireNonNull(aa, "AudioAttributes must not be null");
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
if (aag.supportsAttributes(aa)) {
return aag.getStreamType();
@@ -258,7 +282,7 @@
*/
@SystemApi
public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) {
- Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+ Objects.requireNonNull(aa, "AudioAttributes must not be null");
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
if (aag.supportsAttributes(aa)) {
return true;
@@ -291,7 +315,7 @@
*/
@TestApi
public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
- Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+ Objects.requireNonNull(aa, "AudioAttributes must not be null");
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
if (aag.supportsAttributes(aa)) {
return aag.getVolumeGroupId();
@@ -300,6 +324,17 @@
return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
}
+ private static int getVolumeGroupIdForAudioAttributesInt(@NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes, "attributes must not be null");
+ for (AudioProductStrategy productStrategy : getAudioProductStrategies()) {
+ int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
+ if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
+ return volumeGroupId;
+ }
+ }
+ return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -374,8 +409,8 @@
*/
private static boolean attributesMatches(@NonNull AudioAttributes refAttr,
@NonNull AudioAttributes attr) {
- Preconditions.checkNotNull(refAttr, "refAttr must not be null");
- Preconditions.checkNotNull(attr, "attr must not be null");
+ Objects.requireNonNull(refAttr, "reference AudioAttributes must not be null");
+ Objects.requireNonNull(attr, "requester's AudioAttributes must not be null");
String refFormattedTags = TextUtils.join(";", refAttr.getTags());
String cliFormattedTags = TextUtils.join(";", attr.getTags());
if (refAttr.equals(DEFAULT_ATTRIBUTES)) {
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
new file mode 100644
index 0000000..a792498
--- /dev/null
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+ "presubmit": [
+ {
+ "name": "MediaProjectionTests",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/media/tests/projection/TEST_MAPPING b/media/tests/projection/TEST_MAPPING
deleted file mode 100644
index ddb68af..0000000
--- a/media/tests/projection/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "presubmit": [
- {
- "name": "FrameworksServicesTests",
- "options": [
- {"include-filter": "android.media.projection.mediaprojectiontests"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- }
- ]
-}
diff --git a/opengl/java/android/opengl/Matrix.java b/opengl/java/android/opengl/Matrix.java
index ce3f57e..5ae341b 100644
--- a/opengl/java/android/opengl/Matrix.java
+++ b/opengl/java/android/opengl/Matrix.java
@@ -16,6 +16,8 @@
package android.opengl;
+import androidx.annotation.NonNull;
+
/**
* Matrix math utilities. These methods operate on OpenGL ES format
* matrices and vectors stored in float arrays.
@@ -38,7 +40,11 @@
public class Matrix {
/** Temporary memory for operations that need temporary matrix data. */
- private final static float[] sTemp = new float[32];
+ private static final ThreadLocal<float[]> ThreadTmp = new ThreadLocal() {
+ @Override protected float[] initialValue() {
+ return new float[32];
+ }
+ };
/**
* @deprecated All methods are static, do not instantiate this class.
@@ -46,6 +52,40 @@
@Deprecated
public Matrix() {}
+ private static boolean overlap(
+ float[] a, int aStart, int aLength, float[] b, int bStart, int bLength) {
+ if (a != b) {
+ return false;
+ }
+
+ if (aStart == bStart) {
+ return true;
+ }
+
+ int aEnd = aStart + aLength;
+ int bEnd = bStart + bLength;
+
+ if (aEnd == bEnd) {
+ return true;
+ }
+
+ if (aStart < bStart && bStart < aEnd) {
+ return true;
+ }
+ if (aStart < bEnd && bEnd < aEnd) {
+ return true;
+ }
+
+ if (bStart < aStart && aStart < bEnd) {
+ return true;
+ }
+ if (bStart < aEnd && aEnd < bEnd) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Multiplies two 4x4 matrices together and stores the result in a third 4x4
* matrix. In matrix notation: result = lhs x rhs. Due to the way
@@ -53,9 +93,9 @@
* effect as first multiplying by the rhs matrix, then multiplying by
* the lhs matrix. This is the opposite of what you might expect.
* <p>
- * The same float array may be passed for result, lhs, and/or rhs. However,
- * the result element values are undefined if the result elements overlap
- * either the lhs or rhs elements.
+ * The same float array may be passed for result, lhs, and/or rhs. This
+ * operation is expected to do the correct thing if the result elements
+ * overlap with either of the lhs or rhs elements.
*
* @param result The float array that holds the result.
* @param resultOffset The offset into the result array where the result is
@@ -65,20 +105,101 @@
* @param rhs The float array that holds the right-hand-side matrix.
* @param rhsOffset The offset into the rhs array where the rhs is stored.
*
- * @throws IllegalArgumentException if result, lhs, or rhs are null, or if
- * resultOffset + 16 > result.length or lhsOffset + 16 > lhs.length or
- * rhsOffset + 16 > rhs.length.
+ * @throws IllegalArgumentException under any of the following conditions:
+ * result, lhs, or rhs are null;
+ * resultOffset + 16 > result.length
+ * or lhsOffset + 16 > lhs.length
+ * or rhsOffset + 16 > rhs.length;
+ * resultOffset < 0 or lhsOffset < 0 or rhsOffset < 0
*/
- public static native void multiplyMM(float[] result, int resultOffset,
- float[] lhs, int lhsOffset, float[] rhs, int rhsOffset);
+ public static void multiplyMM(float[] result, int resultOffset,
+ float[] lhs, int lhsOffset, float[] rhs, int rhsOffset) {
+ // error checking
+ if (result == null) {
+ throw new IllegalArgumentException("result == null");
+ }
+ if (lhs == null) {
+ throw new IllegalArgumentException("lhs == null");
+ }
+ if (rhs == null) {
+ throw new IllegalArgumentException("rhs == null");
+ }
+ if (resultOffset < 0) {
+ throw new IllegalArgumentException("resultOffset < 0");
+ }
+ if (lhsOffset < 0) {
+ throw new IllegalArgumentException("lhsOffset < 0");
+ }
+ if (rhsOffset < 0) {
+ throw new IllegalArgumentException("rhsOffset < 0");
+ }
+ if (result.length < resultOffset + 16) {
+ throw new IllegalArgumentException("result.length < resultOffset + 16");
+ }
+ if (lhs.length < lhsOffset + 16) {
+ throw new IllegalArgumentException("lhs.length < lhsOffset + 16");
+ }
+ if (rhs.length < rhsOffset + 16) {
+ throw new IllegalArgumentException("rhs.length < rhsOffset + 16");
+ }
+
+ // Check for overlap between rhs and result or lhs and result
+ if ( overlap(result, resultOffset, 16, lhs, lhsOffset, 16)
+ || overlap(result, resultOffset, 16, rhs, rhsOffset, 16) ) {
+ float[] tmp = ThreadTmp.get();
+ for (int i=0; i<4; i++) {
+ final float rhs_i0 = rhs[ 4*i + 0 + rhsOffset ];
+ float ri0 = lhs[ 0 + lhsOffset ] * rhs_i0;
+ float ri1 = lhs[ 1 + lhsOffset ] * rhs_i0;
+ float ri2 = lhs[ 2 + lhsOffset ] * rhs_i0;
+ float ri3 = lhs[ 3 + lhsOffset ] * rhs_i0;
+ for (int j=1; j<4; j++) {
+ final float rhs_ij = rhs[ 4*i + j + rhsOffset];
+ ri0 += lhs[ 4*j + 0 + lhsOffset ] * rhs_ij;
+ ri1 += lhs[ 4*j + 1 + lhsOffset ] * rhs_ij;
+ ri2 += lhs[ 4*j + 2 + lhsOffset ] * rhs_ij;
+ ri3 += lhs[ 4*j + 3 + lhsOffset ] * rhs_ij;
+ }
+ tmp[ 4*i + 0 ] = ri0;
+ tmp[ 4*i + 1 ] = ri1;
+ tmp[ 4*i + 2 ] = ri2;
+ tmp[ 4*i + 3 ] = ri3;
+ }
+
+ // copy from tmp to result
+ for (int i=0; i < 16; i++) {
+ result[ i + resultOffset ] = tmp[ i ];
+ }
+
+ } else {
+ for (int i=0; i<4; i++) {
+ final float rhs_i0 = rhs[ 4*i + 0 + rhsOffset ];
+ float ri0 = lhs[ 0 + lhsOffset ] * rhs_i0;
+ float ri1 = lhs[ 1 + lhsOffset ] * rhs_i0;
+ float ri2 = lhs[ 2 + lhsOffset ] * rhs_i0;
+ float ri3 = lhs[ 3 + lhsOffset ] * rhs_i0;
+ for (int j=1; j<4; j++) {
+ final float rhs_ij = rhs[ 4*i + j + rhsOffset];
+ ri0 += lhs[ 4*j + 0 + lhsOffset ] * rhs_ij;
+ ri1 += lhs[ 4*j + 1 + lhsOffset ] * rhs_ij;
+ ri2 += lhs[ 4*j + 2 + lhsOffset ] * rhs_ij;
+ ri3 += lhs[ 4*j + 3 + lhsOffset ] * rhs_ij;
+ }
+ result[ 4*i + 0 + resultOffset ] = ri0;
+ result[ 4*i + 1 + resultOffset ] = ri1;
+ result[ 4*i + 2 + resultOffset ] = ri2;
+ result[ 4*i + 3 + resultOffset ] = ri3;
+ }
+ }
+ }
/**
* Multiplies a 4 element vector by a 4x4 matrix and stores the result in a
* 4-element column vector. In matrix notation: result = lhs x rhs
* <p>
* The same float array may be passed for resultVec, lhsMat, and/or rhsVec.
- * However, the resultVec element values are undefined if the resultVec
- * elements overlap either the lhsMat or rhsVec elements.
+ * This operation is expected to do the correct thing if the result elements
+ * overlap with either of the lhs or rhs elements.
*
* @param resultVec The float array that holds the result vector.
* @param resultVecOffset The offset into the result array where the result
@@ -89,14 +210,67 @@
* @param rhsVecOffset The offset into the rhs vector where the rhs vector
* is stored.
*
- * @throws IllegalArgumentException if resultVec, lhsMat,
- * or rhsVec are null, or if resultVecOffset + 4 > resultVec.length
- * or lhsMatOffset + 16 > lhsMat.length or
- * rhsVecOffset + 4 > rhsVec.length.
+ * @throws IllegalArgumentException under any of the following conditions:
+ * resultVec, lhsMat, or rhsVec are null;
+ * resultVecOffset + 4 > resultVec.length
+ * or lhsMatOffset + 16 > lhsMat.length
+ * or rhsVecOffset + 4 > rhsVec.length;
+ * resultVecOffset < 0 or lhsMatOffset < 0 or rhsVecOffset < 0
*/
- public static native void multiplyMV(float[] resultVec,
+ public static void multiplyMV(float[] resultVec,
int resultVecOffset, float[] lhsMat, int lhsMatOffset,
- float[] rhsVec, int rhsVecOffset);
+ float[] rhsVec, int rhsVecOffset) {
+ // error checking
+ if (resultVec == null) {
+ throw new IllegalArgumentException("resultVec == null");
+ }
+ if (lhsMat == null) {
+ throw new IllegalArgumentException("lhsMat == null");
+ }
+ if (rhsVec == null) {
+ throw new IllegalArgumentException("rhsVec == null");
+ }
+ if (resultVecOffset < 0) {
+ throw new IllegalArgumentException("resultVecOffset < 0");
+ }
+ if (lhsMatOffset < 0) {
+ throw new IllegalArgumentException("lhsMatOffset < 0");
+ }
+ if (rhsVecOffset < 0) {
+ throw new IllegalArgumentException("rhsVecOffset < 0");
+ }
+ if (resultVec.length < resultVecOffset + 4) {
+ throw new IllegalArgumentException("resultVec.length < resultVecOffset + 4");
+ }
+ if (lhsMat.length < lhsMatOffset + 16) {
+ throw new IllegalArgumentException("lhsMat.length < lhsMatOffset + 16");
+ }
+ if (rhsVec.length < rhsVecOffset + 4) {
+ throw new IllegalArgumentException("rhsVec.length < rhsVecOffset + 4");
+ }
+
+ float tmp0 = lhsMat[0 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+ lhsMat[0 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+ lhsMat[0 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+ lhsMat[0 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+ float tmp1 = lhsMat[1 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+ lhsMat[1 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+ lhsMat[1 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+ lhsMat[1 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+ float tmp2 = lhsMat[2 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+ lhsMat[2 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+ lhsMat[2 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+ lhsMat[2 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+ float tmp3 = lhsMat[3 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+ lhsMat[3 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+ lhsMat[3 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+ lhsMat[3 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+
+ resultVec[ 0 + resultVecOffset ] = tmp0;
+ resultVec[ 1 + resultVecOffset ] = tmp1;
+ resultVec[ 2 + resultVecOffset ] = tmp2;
+ resultVec[ 3 + resultVecOffset ] = tmp3;
+ }
/**
* Transposes a 4 x 4 matrix.
@@ -537,10 +711,9 @@
public static void rotateM(float[] rm, int rmOffset,
float[] m, int mOffset,
float a, float x, float y, float z) {
- synchronized(sTemp) {
- setRotateM(sTemp, 0, a, x, y, z);
- multiplyMM(rm, rmOffset, m, mOffset, sTemp, 0);
- }
+ float[] tmp = ThreadTmp.get();
+ setRotateM(tmp, 16, a, x, y, z);
+ multiplyMM(rm, rmOffset, m, mOffset, tmp, 16);
}
/**
@@ -556,11 +729,7 @@
*/
public static void rotateM(float[] m, int mOffset,
float a, float x, float y, float z) {
- synchronized(sTemp) {
- setRotateM(sTemp, 0, a, x, y, z);
- multiplyMM(sTemp, 16, m, mOffset, sTemp, 0);
- System.arraycopy(sTemp, 16, m, mOffset, 16);
- }
+ rotateM(m, mOffset, m, mOffset, a, x, y, z);
}
/**
@@ -640,9 +809,14 @@
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param x angle of rotation, in degrees
- * @param y angle of rotation, in degrees
+ * @param y is broken, do not use
* @param z angle of rotation, in degrees
+ *
+ * @deprecated This method is incorrect around the y axis. This method is
+ * deprecated and replaced (below) by setRotateEulerM2 which
+ * behaves correctly
*/
+ @Deprecated
public static void setRotateEulerM(float[] rm, int rmOffset,
float x, float y, float z) {
x *= (float) (Math.PI / 180.0f);
@@ -679,6 +853,64 @@
}
/**
+ * Converts Euler angles to a rotation matrix.
+ *
+ * @param rm returns the result
+ * @param rmOffset index into rm where the result matrix starts
+ * @param x angle of rotation, in degrees
+ * @param y angle of rotation, in degrees
+ * @param z angle of rotation, in degrees
+ *
+ * @throws IllegalArgumentException if rm is null;
+ * or if rmOffset + 16 > rm.length;
+ * rmOffset < 0
+ */
+ public static void setRotateEulerM2(@NonNull float[] rm, int rmOffset,
+ float x, float y, float z) {
+ if (rm == null) {
+ throw new IllegalArgumentException("rm == null");
+ }
+ if (rmOffset < 0) {
+ throw new IllegalArgumentException("rmOffset < 0");
+ }
+ if (rm.length < rmOffset + 16) {
+ throw new IllegalArgumentException("rm.length < rmOffset + 16");
+ }
+
+ x *= (float) (Math.PI / 180.0f);
+ y *= (float) (Math.PI / 180.0f);
+ z *= (float) (Math.PI / 180.0f);
+ float cx = (float) Math.cos(x);
+ float sx = (float) Math.sin(x);
+ float cy = (float) Math.cos(y);
+ float sy = (float) Math.sin(y);
+ float cz = (float) Math.cos(z);
+ float sz = (float) Math.sin(z);
+ float cxsy = cx * sy;
+ float sxsy = sx * sy;
+
+ rm[rmOffset + 0] = cy * cz;
+ rm[rmOffset + 1] = -cy * sz;
+ rm[rmOffset + 2] = sy;
+ rm[rmOffset + 3] = 0.0f;
+
+ rm[rmOffset + 4] = sxsy * cz + cx * sz;
+ rm[rmOffset + 5] = -sxsy * sz + cx * cz;
+ rm[rmOffset + 6] = -sx * cy;
+ rm[rmOffset + 7] = 0.0f;
+
+ rm[rmOffset + 8] = -cxsy * cz + sx * sz;
+ rm[rmOffset + 9] = cxsy * sz + sx * cz;
+ rm[rmOffset + 10] = cx * cy;
+ rm[rmOffset + 11] = 0.0f;
+
+ rm[rmOffset + 12] = 0.0f;
+ rm[rmOffset + 13] = 0.0f;
+ rm[rmOffset + 14] = 0.0f;
+ rm[rmOffset + 15] = 1.0f;
+ }
+
+ /**
* Defines a viewing transformation in terms of an eye point, a center of
* view, and an up vector.
*
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index df4705b..6e2927d 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -17,9 +17,9 @@
<!-- Telephony notification channel name for performance boost notifications. -->
<string name="performance_boost_notification_channel">Performance boost</string>
<!-- Notification title text for the performance boost notification. -->
- <string name="performance_boost_notification_title">%s recommends a performance boost</string>
+ <string name="performance_boost_notification_title">Improve your 5G experience</string>
<!-- Notification detail text for the performance boost notification. -->
- <string name="performance_boost_notification_detail">Buy a performance boost for better network performance</string>
+ <string name="performance_boost_notification_detail">%1$s recommends buying a performance boost plan. Tap to buy through %2$s.</string>
<!-- Notification button text to cancel the performance boost notification. -->
<string name="performance_boost_notification_button_not_now">Not now</string>
<!-- Notification button text to manage the performance boost notification. -->
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 1b02c2b..d4ce5f5 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -178,9 +178,9 @@
return false;
}
- String appName = intent.getStringExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME);
- if (TextUtils.isEmpty(appName)) {
- loge("isIntentValid: empty requesting application name: " + appName);
+ String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
+ if (TextUtils.isEmpty(carrier)) {
+ loge("isIntentValid: empty carrier: " + carrier);
return false;
}
@@ -310,14 +310,14 @@
channel.setBlockable(true);
context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
+
Notification notification =
new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(String.format(res.getString(
- R.string.performance_boost_notification_title),
- intent.getStringExtra(
- SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)))
- .setContentText(res.getString(
- R.string.performance_boost_notification_detail))
+ .setContentTitle(res.getString(
+ R.string.performance_boost_notification_title))
+ .setContentText(String.format(res.getString(
+ R.string.performance_boost_notification_detail), carrier, carrier))
.setSmallIcon(R.drawable.ic_performance_boost)
.setContentIntent(createContentIntent(context, intent, 1))
.setDeleteIntent(intent.getParcelableExtra(
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index 1bf644e..daf0b42 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -48,7 +48,7 @@
@RunWith(AndroidJUnit4.class)
public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> {
- private static final String TAG = "SlicePurchaseActivityTest";
+ private static final String CARRIER = "Some Carrier";
private static final String URL = "file:///android_asset/slice_purchase_test.html";
private static final int PHONE_ID = 0;
@@ -95,7 +95,7 @@
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_URL,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
+ intent.putExtra(SlicePurchaseController.EXTRA_CARRIER, CARRIER);
Intent spiedIntent = spy(intent);
// set up pending intents
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index 568d63c..952789c 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -62,7 +62,7 @@
@RunWith(AndroidJUnit4.class)
public class SlicePurchaseBroadcastReceiverTest {
private static final int PHONE_ID = 0;
- private static final String TAG = "SlicePurchaseBroadcastReceiverTest";
+ private static final String CARRIER = "Some Carrier";
private static final String EXTRA = "EXTRA";
@Mock Intent mIntent;
@@ -136,8 +136,7 @@
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
- doReturn(TAG).when(mIntent).getStringExtra(
- eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+ doReturn(CARRIER).when(mIntent).getStringExtra(eq(SlicePurchaseController.EXTRA_CARRIER));
assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
// set up pending intent
@@ -229,8 +228,7 @@
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
- doReturn(TAG).when(mIntent).getStringExtra(
- eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+ doReturn(CARRIER).when(mIntent).getStringExtra(eq(SlicePurchaseController.EXTRA_CARRIER));
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
}
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 90bb2d1..2cb3468 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -40,6 +40,7 @@
],
platform_apis: true,
+ privileged: true,
kotlincflags: ["-Xjvm-default=enable"],
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index aea51b1..3ec4366a 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -22,5 +22,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
+ "com.android.healthconnect",
],
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index a81e2e3..4d8b89b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -49,7 +49,7 @@
import com.android.settingslib.spa.framework.compose.localNavController
import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.PageEvent
+import com.android.settingslib.spa.framework.util.PageWithEvent
import com.android.settingslib.spa.framework.util.getDestination
import com.android.settingslib.spa.framework.util.getEntryId
import com.android.settingslib.spa.framework.util.getSessionName
@@ -118,32 +118,25 @@
arguments = spp.parameter,
enterTransition = {
slideIntoContainer(
- AnimatedContentScope.SlideDirection.Left,
- animationSpec = slideEffect
+ AnimatedContentScope.SlideDirection.Left, animationSpec = slideEffect
) + fadeIn(animationSpec = fadeEffect)
},
exitTransition = {
slideOutOfContainer(
- AnimatedContentScope.SlideDirection.Left,
- animationSpec = slideEffect
+ AnimatedContentScope.SlideDirection.Left, animationSpec = slideEffect
) + fadeOut(animationSpec = fadeEffect)
},
popEnterTransition = {
slideIntoContainer(
- AnimatedContentScope.SlideDirection.Right,
- animationSpec = slideEffect
+ AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
) + fadeIn(animationSpec = fadeEffect)
},
popExitTransition = {
slideOutOfContainer(
- AnimatedContentScope.SlideDirection.Right,
- animationSpec = slideEffect
+ AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
) + fadeOut(animationSpec = fadeEffect)
},
- ) { navBackStackEntry ->
- spp.PageEvent(navBackStackEntry.arguments)
- spp.Page(navBackStackEntry.arguments)
- }
+ ) { navBackStackEntry -> spp.PageWithEvent(navBackStackEntry.arguments) }
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 0871304..2175e55 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -185,6 +185,8 @@
private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
fun build(): SettingsEntry {
+ val page = fromPage ?: owner
+ val isEnabled = page.isEnabled()
return SettingsEntry(
id = id(),
name = name,
@@ -196,10 +198,10 @@
toPage = toPage,
// attributes
- isAllowSearch = isAllowSearch,
+ isAllowSearch = isEnabled && isAllowSearch,
isSearchDataDynamic = isSearchDataDynamic,
hasMutableStatus = hasMutableStatus,
- hasSliceSupport = hasSliceSupport,
+ hasSliceSupport = isEnabled && hasSliceSupport,
// functions
statusDataImpl = statusDataFn,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 2bfa2a4..a362877 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -94,6 +94,12 @@
return !isCreateBy(NULL_PAGE_NAME) &&
!hasRuntimeParam()
}
+
+ fun isEnabled(): Boolean {
+ if (!SpaEnvironmentFactory.isReady()) return false
+ val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
+ return pageProviderRepository.getProviderOrNull(sppName)?.isEnabled(arguments) ?: false
+ }
}
fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 940005d..42e5f7e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -37,6 +37,14 @@
val parameter: List<NamedNavArgument>
get() = emptyList()
+ /**
+ * The API to indicate whether the page is enabled or not.
+ * During SPA page migration, one can use it to enable certain pages in one release.
+ * When the page is disabled, all its related functionalities, such as browsing, search,
+ * slice provider, are disabled as well.
+ */
+ fun isEnabled(arguments: Bundle?): Boolean = true
+
fun getTitle(arguments: Bundle?): String = displayName
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index 73eae07..22a4563 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -33,13 +33,15 @@
import com.android.settingslib.spa.framework.compose.NavControllerWrapper
@Composable
-internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
+internal fun SettingsPageProvider.PageWithEvent(arguments: Bundle? = null) {
+ if (!isEnabled(arguments)) return
val page = remember(arguments) { createSettingsPage(arguments) }
val navController = LocalNavController.current
LifecycleEffect(
onStart = { page.logPageEvent(LogEvent.PAGE_ENTER, navController) },
onStop = { page.logPageEvent(LogEvent.PAGE_LEAVE, navController) },
)
+ Page(arguments)
}
private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControllerWrapper) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
index bd5884d..218f569 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -30,6 +30,7 @@
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.SppDisabled
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth
@@ -46,12 +47,12 @@
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaLogger = SpaLoggerForTest()
- private val spaEnvironment =
- SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
@Test
fun testBrowsePage() {
spaLogger.reset()
+ val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
SpaEnvironmentFactory.reset(spaEnvironment)
val sppRepository by spaEnvironment.pageProviderRepository
@@ -75,6 +76,24 @@
spaLogger.verifyPageEvent(pageHome.id, 1, 1)
spaLogger.verifyPageEvent(pageLayer1.id, 1, 0)
}
+
+ @Test
+ fun testBrowseDisabledPage() {
+ spaLogger.reset()
+ val spaEnvironment = SpaEnvironmentForTest(
+ context, listOf(SppDisabled.createSettingsPage()), logger = spaLogger
+ )
+ SpaEnvironmentFactory.reset(spaEnvironment)
+
+ val sppRepository by spaEnvironment.pageProviderRepository
+ val sppDisabled = sppRepository.getProviderOrNull("SppDisabled")!!
+ val pageDisabled = sppDisabled.createSettingsPage()
+
+ composeTestRule.setContent { BrowseContent(sppRepository) }
+
+ composeTestRule.onNodeWithText(sppDisabled.getTitle(null)).assertDoesNotExist()
+ spaLogger.verifyPageEvent(pageDisabled.id, 0, 0)
+ }
}
private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index b600ac6..6de1ae5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -16,13 +16,16 @@
package com.android.settingslib.spa.framework.common
+import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.os.bundleOf
+import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.slice.appendSpaParams
import com.android.settingslib.spa.slice.getEntryId
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
import com.android.settingslib.spa.tests.testutils.getUniquePageId
import com.google.common.truth.Truth.assertThat
@@ -53,6 +56,9 @@
@RunWith(AndroidJUnit4::class)
class SettingsEntryTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaEnvironment = SpaEnvironmentForTest(context)
+
@get:Rule
val composeTestRule = createComposeRule()
@@ -77,15 +83,15 @@
val owner = SettingsPage.create("mySpp")
val fromPage = SettingsPage.create("fromSpp")
val toPage = SettingsPage.create("toSpp")
- val entryFrom = SettingsEntryBuilder.createLinkFrom("myEntry", owner)
- .setLink(toPage = toPage).build()
+ val entryFrom =
+ SettingsEntryBuilder.createLinkFrom("myEntry", owner).setLink(toPage = toPage).build()
assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
assertThat(entryFrom.displayName).isEqualTo("myEntry")
assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
- val entryTo = SettingsEntryBuilder.createLinkTo("myEntry", owner)
- .setLink(fromPage = fromPage).build()
+ val entryTo =
+ SettingsEntryBuilder.createLinkTo("myEntry", owner).setLink(fromPage = fromPage).build()
assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
assertThat(entryTo.displayName).isEqualTo("myEntry")
assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
@@ -98,9 +104,7 @@
val entryInject = SettingsEntryBuilder.createInject(owner).build()
assertThat(entryInject.id).isEqualTo(
getUniqueEntryId(
- INJECT_ENTRY_NAME_TEST,
- owner,
- toPage = owner
+ INJECT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME_TEST}_mySpp")
@@ -114,9 +118,7 @@
val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
assertThat(entryInject.id).isEqualTo(
getUniqueEntryId(
- ROOT_ENTRY_NAME_TEST,
- owner,
- toPage = owner
+ ROOT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
assertThat(entryInject.displayName).isEqualTo("myRootEntry")
@@ -126,13 +128,15 @@
@Test
fun testSetAttributes() {
- val owner = SettingsPage.create("mySpp")
- val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
- .setDisplayName("myEntryDisplay")
- .setIsSearchDataDynamic(false)
- .setHasMutableStatus(true)
- .setSearchDataFn { null }
- .setSliceDataFn { _, _ -> null }
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val owner = SettingsPage.create("SppHome")
+ val entryBuilder =
+ SettingsEntryBuilder.create(owner, "myEntry")
+ .setDisplayName("myEntryDisplay")
+ .setIsSearchDataDynamic(false)
+ .setHasMutableStatus(true)
+ .setSearchDataFn { null }
+ .setSliceDataFn { _, _ -> null }
val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
assertThat(entry.displayName).isEqualTo("myEntryDisplay")
@@ -143,21 +147,52 @@
assertThat(entry.hasMutableStatus).isTrue()
assertThat(entry.hasSliceSupport).isTrue()
+ // Test disabled Spp
+ val ownerDisabled = SettingsPage.create("SppDisabled")
+ val entryBuilderDisabled =
+ SettingsEntryBuilder.create(ownerDisabled, "myEntry")
+ .setDisplayName("myEntryDisplay")
+ .setIsSearchDataDynamic(false)
+ .setHasMutableStatus(true)
+ .setSearchDataFn { null }
+ .setSliceDataFn { _, _ -> null }
+ val entryDisabled = entryBuilderDisabled.build()
+ assertThat(entryDisabled.id).isEqualTo(getUniqueEntryId("myEntry", ownerDisabled))
+ assertThat(entryDisabled.displayName).isEqualTo("myEntryDisplay")
+ assertThat(entryDisabled.fromPage).isNull()
+ assertThat(entryDisabled.toPage).isNull()
+ assertThat(entryDisabled.isAllowSearch).isFalse()
+ assertThat(entryDisabled.isSearchDataDynamic).isFalse()
+ assertThat(entryDisabled.hasMutableStatus).isTrue()
+ assertThat(entryDisabled.hasSliceSupport).isFalse()
+
+ // Clear search data fn
val entry2 = entryBuilder.clearSearchDataFn().build()
assertThat(entry2.isAllowSearch).isFalse()
+
+ // Clear SppHome in spa environment
+ SpaEnvironmentFactory.reset()
+ val entry3 = entryBuilder.build()
+ assertThat(entry3.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry3.displayName).isEqualTo("myEntryDisplay")
+ assertThat(entry3.fromPage).isNull()
+ assertThat(entry3.toPage).isNull()
+ assertThat(entry3.isAllowSearch).isFalse()
+ assertThat(entry3.isSearchDataDynamic).isFalse()
+ assertThat(entry3.hasMutableStatus).isTrue()
+ assertThat(entry3.hasSliceSupport).isFalse()
}
@Test
fun testSetMarco() {
- val owner = SettingsPage.create("mySpp", arguments = bundleOf("param" to "v1"))
- val entry = SettingsEntryBuilder.create(owner, "myEntry")
- .setMacro {
- assertThat(it?.getString("param")).isEqualTo("v1")
- assertThat(it?.getString("rtParam")).isEqualTo("v2")
- assertThat(it?.getString("unknown")).isNull()
- MacroForTest(getUniquePageId("mySpp"), getUniqueEntryId("myEntry", owner))
- }
- .build()
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val owner = SettingsPage.create("SppHome", arguments = bundleOf("param" to "v1"))
+ val entry = SettingsEntryBuilder.create(owner, "myEntry").setMacro {
+ assertThat(it?.getString("param")).isEqualTo("v1")
+ assertThat(it?.getString("rtParam")).isEqualTo("v2")
+ assertThat(it?.getString("unknown")).isNull()
+ MacroForTest(getUniquePageId("SppHome"), getUniqueEntryId("myEntry", owner))
+ }.build()
val rtArguments = bundleOf("rtParam" to "v2")
composeTestRule.setContent { entry.UiLayout(rtArguments) }
@@ -175,14 +210,14 @@
@Test
fun testSetSliceDataFn() {
- val owner = SettingsPage.create("mySpp")
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val owner = SettingsPage.create("SppHome")
val entryId = getUniqueEntryId("myEntry", owner)
val emptySliceData = EntrySliceData()
- val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
- .setSliceDataFn { uri, _ ->
- return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null
- }
+ val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry").setSliceDataFn { uri, _ ->
+ return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null
+ }
val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(entryId)
assertThat(entry.hasSliceSupport).isTrue()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 1bdba29..530d2ed 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -23,6 +23,7 @@
import androidx.slice.Slice
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppHome
@@ -44,6 +45,8 @@
@Test
fun getOrBuildSliceDataTest() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+
// Slice empty
assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
@@ -67,6 +70,8 @@
@Test
fun getActiveSliceDataTest() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+
val page = SppLayer2.createSettingsPage()
val entryId = getUniqueEntryId("Layer2Entry1", page)
val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index f38bd08..2755b4e 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -92,6 +92,23 @@
}
}
+object SppDisabled : SettingsPageProvider {
+ override val name = "SppDisabled"
+
+ override fun isEnabled(arguments: Bundle?): Boolean = false
+
+ override fun getTitle(arguments: Bundle?): String {
+ return "TitleDisabled"
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SppLayer1.buildInject().setLink(fromPage = owner).build(),
+ )
+ }
+}
+
object SppLayer1 : SettingsPageProvider {
override val name = "SppLayer1"
@@ -190,7 +207,7 @@
SettingsPageProviderRepository(
listOf(
SppHome, SppLayer1, SppLayer2,
- SppForSearch,
+ SppForSearch, SppDisabled,
object : SettingsPageProvider {
override val name = "SppWithParam"
override val parameter = listOf(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index cbb4fbe..ce0c551 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -26,6 +26,7 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.runBlocking
/**
* The config used to load the App List.
@@ -47,8 +48,21 @@
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
): Flow<(app: ApplicationInfo) -> Boolean>
+
+ /** Gets the system app package names. */
+ fun getSystemPackageNamesBlocking(config: AppListConfig): Set<String>
}
+/**
+ * Util for app list repository.
+ */
+object AppListRepositoryUtil {
+ /** Gets the system app package names. */
+ @JvmStatic
+ fun getSystemPackageNames(context: Context, config: AppListConfig): Set<String> {
+ return AppListRepositoryImpl(context).getSystemPackageNamesBlocking(config)
+ }
+}
internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
private val packageManager = context.packageManager
@@ -83,15 +97,26 @@
): Flow<(app: ApplicationInfo) -> Boolean> =
userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
+ override fun getSystemPackageNamesBlocking(config: AppListConfig) = runBlocking {
+ getSystemPackageNames(config)
+ }
+
+ private suspend fun getSystemPackageNames(config: AppListConfig): Set<String> =
+ coroutineScope {
+ val loadAppsDeferred = async { loadApps(config) }
+ val homeOrLauncherPackages = loadHomeOrLauncherPackages(config.userId)
+ val showSystemPredicate =
+ { app: ApplicationInfo -> isSystemApp(app, homeOrLauncherPackages) }
+ loadAppsDeferred.await().filter(showSystemPredicate).map { it.packageName }.toSet()
+ }
+
private suspend fun showSystemPredicate(
userId: Int,
showSystem: Boolean,
): (app: ApplicationInfo) -> Boolean {
if (showSystem) return { true }
val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
- return { app ->
- app.isUpdatedSystemApp || !app.isSystemApp || app.packageName in homeOrLauncherPackages
- }
+ return { app -> !isSystemApp(app, homeOrLauncherPackages) }
}
private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> {
@@ -117,6 +142,11 @@
}
}
+ private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean {
+ return !app.isUpdatedSystemApp && app.isSystemApp &&
+ !(app.packageName in homeOrLauncherPackages)
+ }
+
companion object {
private fun ApplicationInfo.isInAppList(
showInstantApps: Boolean,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index df828f2..6cd1c0d 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -51,7 +51,7 @@
}
internal interface IAppListViewModel<T : AppRecord> {
- val option: StateFlowBridge<Int?>
+ val optionFlow: MutableStateFlow<Int?>
val spinnerOptionsFlow: Flow<List<SpinnerOption>>
val appListDataFlow: Flow<AppListData<T>>
}
@@ -69,7 +69,7 @@
val appListConfig = StateFlowBridge<AppListConfig>()
val listModel = StateFlowBridge<AppListModel<T>>()
val showSystem = StateFlowBridge<Boolean>()
- final override val option = StateFlowBridge<Int?>()
+ final override val optionFlow = MutableStateFlow<Int?>(null)
val searchQuery = StateFlowBridge<String>()
private val appListRepository = appListRepositoryFactory(application)
@@ -97,7 +97,7 @@
listModel.getSpinnerOptions(recordList)
}
- override val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
+ override val appListDataFlow = optionFlow.filterNotNull().flatMapLatest(::filterAndSort)
.combine(searchQuery.flow) { appListData, searchQuery ->
appListData.filter {
it.label.contains(other = searchQuery, ignoreCase = true)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 34c3ee0..7199e3a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -24,11 +24,10 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
@@ -37,7 +36,6 @@
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.util.StateFlowBridge
import com.android.settingslib.spa.widget.ui.CategoryTitle
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spa.widget.ui.Spinner
@@ -52,6 +50,7 @@
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
private const val TAG = "AppList"
private const val CONTENT_TYPE_HEADER = "header"
@@ -88,7 +87,7 @@
val viewModel = viewModelSupplier()
Column(Modifier.fillMaxSize()) {
val optionsState = viewModel.spinnerOptionsFlow.collectAsState(null, Dispatchers.IO)
- SpinnerOptions(optionsState, viewModel.option)
+ SpinnerOptions(optionsState, viewModel.optionFlow)
val appListData = viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
listModel.AppListWidget(appListData, header, bottomPadding, noItemMessage)
}
@@ -97,15 +96,18 @@
@Composable
private fun SpinnerOptions(
optionsState: State<List<SpinnerOption>?>,
- optionBridge: StateFlowBridge<Int?>,
+ optionFlow: MutableStateFlow<Int?>,
) {
val options = optionsState.value
- val selectedOption = rememberSaveable(options) {
- mutableStateOf(options?.let { it.firstOrNull()?.id ?: -1 })
+ LaunchedEffect(options) {
+ if (options != null && !options.any { it.id == optionFlow.value }) {
+ // Reset to first option if the available options changed, and the current selected one
+ // does not in the new options.
+ optionFlow.value = options.let { it.firstOrNull()?.id ?: -1 }
+ }
}
- optionBridge.Sync(selectedOption)
if (options != null) {
- Spinner(options, selectedOption.value) { selectedOption.value = it }
+ Spinner(options, optionFlow.collectAsState().value) { optionFlow.value = it }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 2d8f009..b0ea40a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -180,9 +180,7 @@
@Test
fun showSystemPredicate_showSystem() = runTest {
- val app = ApplicationInfo().apply {
- flags = ApplicationInfo.FLAG_SYSTEM
- }
+ val app = SYSTEM_APP
val showSystemPredicate = getShowSystemPredicate(showSystem = true)
@@ -191,9 +189,7 @@
@Test
fun showSystemPredicate_notShowSystemAndIsSystemApp() = runTest {
- val app = ApplicationInfo().apply {
- flags = ApplicationInfo.FLAG_SYSTEM
- }
+ val app = SYSTEM_APP
val showSystemPredicate = getShowSystemPredicate(showSystem = false)
@@ -202,9 +198,7 @@
@Test
fun showSystemPredicate_isUpdatedSystemApp() = runTest {
- val app = ApplicationInfo().apply {
- flags = ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
- }
+ val app = UPDATED_SYSTEM_APP
val showSystemPredicate = getShowSystemPredicate(showSystem = false)
@@ -213,10 +207,8 @@
@Test
fun showSystemPredicate_isHome() = runTest {
- val app = ApplicationInfo().apply {
- flags = ApplicationInfo.FLAG_SYSTEM
- packageName = "home.app"
- }
+ val app = HOME_APP
+
whenever(packageManager.getHomeActivities(any())).thenAnswer {
@Suppress("UNCHECKED_CAST")
val resolveInfos = it.arguments[0] as MutableList<ResolveInfo>
@@ -231,10 +223,8 @@
@Test
fun showSystemPredicate_appInLauncher() = runTest {
- val app = ApplicationInfo().apply {
- flags = ApplicationInfo.FLAG_SYSTEM
- packageName = "app.in.launcher"
- }
+ val app = IN_LAUMCHER_APP
+
whenever(
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
@@ -244,6 +234,17 @@
assertThat(showSystemPredicate(app)).isTrue()
}
+ @Test
+ fun getSystemPackageNames_returnExpectedValues() = runTest {
+ mockInstalledApplications(listOf(
+ NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUMCHER_APP))
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(context, appListConfig)
+
+ assertThat(systemPackageNames).containsExactly("system.app", "home.app", "app.in.launcher")
+ }
+
private suspend fun getShowSystemPredicate(showSystem: Boolean) =
repository.showSystemPredicate(
userIdFlow = flowOf(USER_ID),
@@ -264,6 +265,26 @@
privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
}
+ val SYSTEM_APP = ApplicationInfo().apply {
+ packageName = "system.app"
+ flags = ApplicationInfo.FLAG_SYSTEM
+ }
+
+ val UPDATED_SYSTEM_APP = ApplicationInfo().apply {
+ packageName = "updated.system.app"
+ flags = ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
+ }
+
+ val HOME_APP = ApplicationInfo().apply {
+ packageName = "home.app"
+ flags = ApplicationInfo.FLAG_SYSTEM
+ }
+
+ val IN_LAUMCHER_APP = ApplicationInfo().apply {
+ packageName = "app.in.launcher"
+ flags = ApplicationInfo.FLAG_SYSTEM
+ }
+
fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
activityInfo = ActivityInfo().apply {
this.packageName = packageName
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index f514487..b1d02f5 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -56,7 +56,7 @@
viewModel.appListConfig.setIfAbsent(CONFIG)
viewModel.listModel.setIfAbsent(listModel)
viewModel.showSystem.setIfAbsent(false)
- viewModel.option.setIfAbsent(0)
+ viewModel.optionFlow.value = 0
viewModel.searchQuery.setIfAbsent("")
viewModel.reloadApps()
return viewModel
@@ -90,6 +90,8 @@
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
+
+ override fun getSystemPackageNamesBlocking(config: AppListConfig): Set<String> = setOf()
}
private object FakeAppRepository : AppRepository {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 2a1f7a4..d5c7c19 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -29,7 +29,6 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.util.StateFlowBridge
import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppEntry
@@ -38,6 +37,7 @@
import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
@@ -125,7 +125,7 @@
bottomPadding = 0.dp,
).AppListImpl {
object : IAppListViewModel<TestAppRecord> {
- override val option: StateFlowBridge<Int?> = StateFlowBridge()
+ override val optionFlow: MutableStateFlow<Int?> = MutableStateFlow(null)
override val spinnerOptionsFlow = flowOf(options.mapIndexed { index, option ->
SpinnerOption(id = index, text = option)
})
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
index 1f72609..a80061e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -53,8 +53,8 @@
static final String PREFIX_BT_SYNC_INTERVAL = "SI:";
static final String PREFIX_BT_IS_ENCRYPTED = "E:";
static final String PREFIX_BT_BROADCAST_CODE = "C:";
- static final String PREFIX_BT_PRESENTATION_DELAY = "D:";
- static final String PREFIX_BT_SUBGROUPS = "G:";
+ static final String PREFIX_BT_PRESENTATION_DELAY = "PD:";
+ static final String PREFIX_BT_SUBGROUPS = "SG:";
static final String PREFIX_BT_ANDROID_VERSION = "V:";
// BluetoothLeBroadcastSubgroup
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
index aff9a6e..c61ebc0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
@@ -28,7 +28,9 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -38,6 +40,43 @@
private static final String METADATA_START = "<";
private static final String METADATA_END = ">";
private static final String PATTERN_REGEX = "<(.*?)>";
+ private static final String PATTERN_BT_BROADCAST_METADATA =
+ "T:<(.*?)>;+D:<(.*?)>;+AS:<(.*?)>;+B:<(.*?)>;+SI:<(.*?)>;+E:<(.*?)>;+C:<(.*?)>;"
+ + "+PD:<(.*?)>;+SG:(.*)";
+ private static final String PATTERN_BT_SUBGROUP =
+ "CID:<(.*?)>;+CC:<(.*?);>;+AC:<(.*?);>;+CP:<(.*?)>;+BC:<(.*)>;>;";
+ private static final String PATTERN_BT_CHANNEL = "CI:<(.*?)>;+BCCM:<(.*?);>;";
+
+ /* Index for BluetoothLeBroadcastMetadata */
+ private static int MATCH_INDEX_ADDRESS_TYPE = 1;
+ private static int MATCH_INDEX_DEVICE = 2;
+ private static int MATCH_INDEX_ADVERTISING_SID = 3;
+ private static int MATCH_INDEX_BROADCAST_ID = 4;
+ private static int MATCH_INDEX_SYNC_INTERVAL = 5;
+ private static int MATCH_INDEX_IS_ENCRYPTED = 6;
+ private static int MATCH_INDEX_BROADCAST_CODE = 7;
+ private static int MATCH_INDEX_PRESENTATION_DELAY = 8;
+ private static int MATCH_INDEX_SUBGROUPS = 9;
+
+ /* Index for BluetoothLeBroadcastSubgroup */
+ private static int MATCH_INDEX_CODEC_ID = 1;
+ private static int MATCH_INDEX_CODEC_CONFIG = 2;
+ private static int MATCH_INDEX_AUDIO_CONTENT = 3;
+ private static int MATCH_INDEX_CHANNEL_PREF = 4;
+ private static int MATCH_INDEX_BROADCAST_CHANNEL = 5;
+
+ /* Index for BluetoothLeAudioCodecConfigMetadata */
+ private static int LIST_INDEX_AUDIO_LOCATION = 0;
+ private static int LIST_INDEX_CODEC_CONFIG_RAW_METADATA = 1;
+
+ /* Index for BluetoothLeAudioContentMetadata */
+ private static int LIST_INDEX_PROGRAM_INFO = 0;
+ private static int LIST_INDEX_LANGUAGE = 1;
+ private static int LIST_INDEX_AUDIO_CONTENT_RAW_METADATA = 2;
+
+ /* Index for BluetoothLeBroadcastChannel */
+ private static int MATCH_INDEX_CHANNEL_INDEX = 1;
+ private static int MATCH_INDEX_CHANNEL_CODEC_CONFIG = 2;
private BluetoothLeBroadcastSubgroup mSubgroup;
private List<BluetoothLeBroadcastSubgroup> mSubgroupList;
@@ -55,17 +94,20 @@
private byte[] mBroadcastCode;
// BluetoothLeBroadcastSubgroup
- private long mCodecId;
+ private int mCodecId;
private BluetoothLeAudioContentMetadata mContentMetadata;
private BluetoothLeAudioCodecConfigMetadata mConfigMetadata;
- private BluetoothLeBroadcastChannel mChannel;
+ private Boolean mNoChannelPreference;
+ private List<BluetoothLeBroadcastChannel> mChannel;
// BluetoothLeAudioCodecConfigMetadata
private long mAudioLocation;
+ private byte[] mCodecConfigMetadata;
// BluetoothLeAudioContentMetadata
private String mLanguage;
private String mProgramInfo;
+ private byte[] mAudioContentMetadata;
// BluetoothLeBroadcastChannel
private boolean mIsSelected;
@@ -135,6 +177,7 @@
for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) {
String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig());
String audioContent = convertAudioContentToString(subgroup.getContentMetadata());
+ boolean hasChannelPreference = subgroup.hasChannelPreference();
String channels = convertChannelToString(subgroup.getChannels());
subgroupString = new StringBuilder()
.append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID)
@@ -146,6 +189,9 @@
.append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT)
.append(METADATA_START).append(audioContent).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_CHANNEL_PREF)
+ .append(METADATA_START).append(hasChannelPreference).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL)
.append(METADATA_START).append(channels).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
@@ -211,26 +257,35 @@
if (DEBUG) {
Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata");
}
- Pattern pattern = Pattern.compile(PATTERN_REGEX);
+
+ Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA);
Matcher match = pattern.matcher(qrCodeString);
if (match.find()) {
- ArrayList<String> resultList = new ArrayList<>();
- resultList.add(match.group(1));
- mSourceAddressType = Integer.parseInt(resultList.get(0));
+ mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE));
mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
- resultList.get(1));
- mSourceAdvertisingSid = Integer.parseInt(resultList.get(2));
- mBroadcastId = Integer.parseInt(resultList.get(3));
- mPaSyncInterval = Integer.parseInt(resultList.get(4));
- mIsEncrypted = Boolean.valueOf(resultList.get(5));
- mBroadcastCode = resultList.get(6).getBytes();
- mPresentationDelayMicros = Integer.parseInt(resultList.get(7));
- mSubgroup = convertToSubgroup(resultList.get(8));
+ match.group(MATCH_INDEX_DEVICE));
+ mSourceAdvertisingSid = Integer.parseInt(match.group(MATCH_INDEX_ADVERTISING_SID));
+ mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID));
+ mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL));
+ mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED));
+ mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes();
+ mPresentationDelayMicros =
+ Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY));
if (DEBUG) {
- Log.d(TAG, "Converted qrCodeString result: " + match.group());
+ Log.d(TAG, "Converted qrCodeString result: "
+ + " ,Type = " + mSourceAddressType
+ + " ,Device = " + mSourceDevice
+ + " ,AdSid = " + mSourceAdvertisingSid
+ + " ,BroadcastId = " + mBroadcastId
+ + " ,paSync = " + mPaSyncInterval
+ + " ,encrypted = " + mIsEncrypted
+ + " ,BroadcastCode = " + Arrays.toString(mBroadcastCode)
+ + " ,delay = " + mPresentationDelayMicros);
}
+ mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS));
+
return new BluetoothLeBroadcastMetadata.Builder()
.setSourceDevice(mSourceDevice, mSourceAddressType)
.setSourceAdvertisingSid(mSourceAdvertisingSid)
@@ -254,26 +309,26 @@
if (DEBUG) {
Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup");
}
- Pattern pattern = Pattern.compile(PATTERN_REGEX);
+ Pattern pattern = Pattern.compile(PATTERN_BT_SUBGROUP);
Matcher match = pattern.matcher(subgroupString);
if (match.find()) {
- ArrayList<String> resultList = new ArrayList<>();
- resultList.add(match.group(1));
- mCodecId = Long.getLong(resultList.get(0));
- mConfigMetadata = convertToConfigMetadata(resultList.get(1));
- mContentMetadata = convertToContentMetadata(resultList.get(2));
- mChannel = convertToChannel(resultList.get(3), mConfigMetadata);
+ mCodecId = Integer.parseInt(match.group(MATCH_INDEX_CODEC_ID));
+ mConfigMetadata = convertToConfigMetadata(match.group(MATCH_INDEX_CODEC_CONFIG));
+ mContentMetadata = convertToContentMetadata(match.group(MATCH_INDEX_AUDIO_CONTENT));
+ mNoChannelPreference = Boolean.valueOf(match.group(MATCH_INDEX_CHANNEL_PREF));
+ mChannel =
+ convertToChannel(match.group(MATCH_INDEX_BROADCAST_CHANNEL), mConfigMetadata);
- if (DEBUG) {
- Log.d(TAG, "Converted subgroupString result: " + match.group());
+ BluetoothLeBroadcastSubgroup.Builder subgroupBuilder =
+ new BluetoothLeBroadcastSubgroup.Builder();
+ subgroupBuilder.setCodecId(mCodecId);
+ subgroupBuilder.setCodecSpecificConfig(mConfigMetadata);
+ subgroupBuilder.setContentMetadata(mContentMetadata);
+
+ for (BluetoothLeBroadcastChannel channel : mChannel) {
+ subgroupBuilder.addChannel(channel);
}
-
- return new BluetoothLeBroadcastSubgroup.Builder()
- .setCodecId(mCodecId)
- .setCodecSpecificConfig(mConfigMetadata)
- .setContentMetadata(mContentMetadata)
- .addChannel(mChannel)
- .build();
+ return subgroupBuilder.build();
} else {
if (DEBUG) {
Log.d(TAG,
@@ -291,15 +346,17 @@
}
Pattern pattern = Pattern.compile(PATTERN_REGEX);
Matcher match = pattern.matcher(configMetadataString);
- if (match.find()) {
- ArrayList<String> resultList = new ArrayList<>();
+ ArrayList<String> resultList = new ArrayList<>();
+ while (match.find()) {
resultList.add(match.group(1));
- mAudioLocation = Long.getLong(resultList.get(0));
-
- if (DEBUG) {
- Log.d(TAG, "Converted configMetadataString result: " + match.group());
- }
-
+ Log.d(TAG, "Codec Config match : " + match.group(1));
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Converted configMetadataString result: " + resultList.size());
+ }
+ if (resultList.size() > 0) {
+ mAudioLocation = Long.parseLong(resultList.get(LIST_INDEX_AUDIO_LOCATION));
+ mCodecConfigMetadata = resultList.get(LIST_INDEX_CODEC_CONFIG_RAW_METADATA).getBytes();
return new BluetoothLeAudioCodecConfigMetadata.Builder()
.setAudioLocation(mAudioLocation)
.build();
@@ -319,14 +376,25 @@
}
Pattern pattern = Pattern.compile(PATTERN_REGEX);
Matcher match = pattern.matcher(contentMetadataString);
- if (match.find()) {
- ArrayList<String> resultList = new ArrayList<>();
+ ArrayList<String> resultList = new ArrayList<>();
+ while (match.find()) {
+ Log.d(TAG, "Audio Content match : " + match.group(1));
resultList.add(match.group(1));
- mProgramInfo = resultList.get(0);
- mLanguage = resultList.get(1);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Converted contentMetadataString result: " + resultList.size());
+ }
+ if (resultList.size() > 0) {
+ mProgramInfo = resultList.get(LIST_INDEX_PROGRAM_INFO);
+ mLanguage = resultList.get(LIST_INDEX_LANGUAGE);
+ mAudioContentMetadata =
+ resultList.get(LIST_INDEX_AUDIO_CONTENT_RAW_METADATA).getBytes();
- if (DEBUG) {
- Log.d(TAG, "Converted contentMetadataString result: " + match.group());
+ /* TODO(b/265253566) : Need to set the default value for language when the user starts
+ * the broadcast.
+ */
+ if (mLanguage.equals("null")) {
+ mLanguage = "eng";
}
return new BluetoothLeAudioContentMetadata.Builder()
@@ -342,28 +410,34 @@
}
}
- private BluetoothLeBroadcastChannel convertToChannel(String channelString,
+ private List<BluetoothLeBroadcastChannel> convertToChannel(String channelString,
BluetoothLeAudioCodecConfigMetadata configMetadata) {
if (DEBUG) {
Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel");
}
- Pattern pattern = Pattern.compile(PATTERN_REGEX);
+ Pattern pattern = Pattern.compile(PATTERN_BT_CHANNEL);
Matcher match = pattern.matcher(channelString);
- if (match.find()) {
- ArrayList<String> resultList = new ArrayList<>();
- resultList.add(match.group(1));
- mIsSelected = Boolean.valueOf(resultList.get(0));
- mChannelIndex = Integer.parseInt(resultList.get(1));
+ Map<Integer, BluetoothLeAudioCodecConfigMetadata> channel =
+ new HashMap<Integer, BluetoothLeAudioCodecConfigMetadata>();
+ while (match.find()) {
+ channel.put(Integer.parseInt(match.group(MATCH_INDEX_CHANNEL_INDEX)),
+ convertToConfigMetadata(match.group(MATCH_INDEX_CHANNEL_CODEC_CONFIG)));
+ }
- if (DEBUG) {
- Log.d(TAG, "Converted channelString result: " + match.group());
+ if (channel.size() > 0) {
+ mIsSelected = false;
+ ArrayList<BluetoothLeBroadcastChannel> broadcastChannelList = new ArrayList<>();
+ for (Map.Entry<Integer, BluetoothLeAudioCodecConfigMetadata> entry :
+ channel.entrySet()) {
+
+ broadcastChannelList.add(
+ new BluetoothLeBroadcastChannel.Builder()
+ .setSelected(mIsSelected)
+ .setChannelIndex(entry.getKey())
+ .setCodecMetadata(entry.getValue())
+ .build());
}
-
- return new BluetoothLeBroadcastChannel.Builder()
- .setSelected(mIsSelected)
- .setChannelIndex(mChannelIndex)
- .setCodecMetadata(configMetadata)
- .build();
+ return broadcastChannelList;
} else {
if (DEBUG) {
Log.d(TAG,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5ee36f3..4365a9b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -695,7 +695,6 @@
Settings.Secure.BACKUP_AUTO_RESTORE,
Settings.Secure.BACKUP_ENABLED,
Settings.Secure.BACKUP_PROVISIONED,
- Settings.Secure.BACKUP_SCHEDULING_ENABLED,
Settings.Secure.BACKUP_TRANSPORT,
Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d716b32..c641a85 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -620,6 +620,8 @@
<uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" />
<uses-permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
+ <uses-permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" />
+
<!-- Permission required for CTS test - KeyguardLockedStateApiTest -->
<uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 854d96e..697e181 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -272,6 +272,7 @@
"LowLightDreamLib",
"motion_tool_lib",
"androidx.core_core-animation-testing-nodeps",
+ "androidx.compose.ui_ui",
],
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 90d3488..115cf792 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -343,6 +343,7 @@
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
+ <protected-broadcast android:name="com.android.systemui.STARTED" />
<application
android:name=".SystemUIApplication"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index a450d3a..9a9236b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -791,13 +791,13 @@
// Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
// one-off synchronization to make sure that this is done in sync between the two different
// windows.
+ controller.startDrawingInOverlayOf(decorView)
synchronizeNextDraw(
then = {
isSourceDrawnInDialog = true
maybeStartLaunchAnimation()
}
)
- controller.startDrawingInOverlayOf(decorView)
}
/**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 0028d13..dfac02d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -195,14 +195,16 @@
backgroundDrawable = WrappedDrawable(background)
backgroundView?.background = backgroundDrawable
+ // Delay the calls to `ghostedView.setVisibility()` during the animation. This must be
+ // called before `GhostView.addGhost()` is called because the latter will change the
+ // *transition* visibility, which won't be blocked and will affect the normal View
+ // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration.
+ (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+
// Create a ghost of the view that will be moving and fading out. This allows to fade out
// the content before fading out the background.
ghostView = GhostView.addGhost(ghostedView, launchContainer)
- // The ghost was just created, so ghostedView is currently invisible. We need to make sure
- // that it stays invisible as long as we are animating.
- (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
-
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
@@ -297,14 +299,19 @@
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
GhostView.removeGhost(ghostedView)
- (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
launchContainerOverlay.remove(backgroundView)
- // Make sure that the view is considered VISIBLE by accessibility by first making it
- // INVISIBLE then VISIBLE (see b/204944038#comment17 for more info).
- ghostedView.visibility = View.INVISIBLE
- ghostedView.visibility = View.VISIBLE
- ghostedView.invalidate()
+ if (ghostedView is LaunchableView) {
+ // Restore the ghosted view visibility.
+ ghostedView.setShouldBlockVisibilityChanges(false)
+ } else {
+ // Make the ghosted view visible. We ensure that the view is considered VISIBLE by
+ // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
+ // for more info).
+ ghostedView.visibility = View.INVISIBLE
+ ghostedView.visibility = View.VISIBLE
+ ghostedView.invalidate()
+ }
}
companion object {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index 67b59e0..774255b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -21,15 +21,19 @@
/** A view that can expand/launch into an app or a dialog. */
interface LaunchableView {
/**
- * Set whether this view should block/postpone all visibility changes. This ensures that this
- * view:
+ * Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures
+ * that this view:
* - remains invisible during the launch animation given that it is ghosted and already drawn
* somewhere else.
* - remains invisible as long as a dialog expanded from it is shown.
* - restores its expected visibility once the dialog expanded from it is dismissed.
*
- * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and
- * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
+ * When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should
+ * be restored to its expected value, i.e. it should have the visibility of the last call to
+ * `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any,
+ * or the original view visibility otherwise.
+ *
+ * Note that calls to [View.setTransitionVisibility] shouldn't be blocked.
*
* @param block whether we should block/postpone all calls to `setVisibility` and
* `setTransitionVisibility`.
@@ -46,27 +50,31 @@
* super.setVisibility(visibility).
*/
private val superSetVisibility: (Int) -> Unit,
-
- /**
- * The lambda that should set the actual transition visibility of [view], usually by calling
- * super.setTransitionVisibility(visibility).
- */
- private val superSetTransitionVisibility: (Int) -> Unit,
-) {
+) : LaunchableView {
private var blockVisibilityChanges = false
private var lastVisibility = view.visibility
/** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */
- fun setShouldBlockVisibilityChanges(block: Boolean) {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
if (block == blockVisibilityChanges) {
return
}
blockVisibilityChanges = block
if (block) {
+ // Save the current visibility for later.
lastVisibility = view.visibility
} else {
- superSetVisibility(lastVisibility)
+ // Restore the visibility. To avoid accessibility issues, we change the visibility twice
+ // which makes sure that we trigger a visibility flag change (see b/204944038#comment17
+ // for more info).
+ if (lastVisibility == View.VISIBLE) {
+ superSetVisibility(View.INVISIBLE)
+ superSetVisibility(View.VISIBLE)
+ } else {
+ superSetVisibility(View.VISIBLE)
+ superSetVisibility(lastVisibility)
+ }
}
}
@@ -79,16 +87,4 @@
superSetVisibility(visibility)
}
-
- /** Call this when [View.setTransitionVisibility] is called. */
- fun setTransitionVisibility(visibility: Int) {
- if (blockVisibilityChanges) {
- // View.setTransitionVisibility just sets the visibility flag, so we don't have to save
- // the transition visibility separately from the normal visibility.
- lastVisibility = visibility
- return
- }
-
- superSetTransitionVisibility(visibility)
- }
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 964ef8c..46d5a5c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -34,23 +34,29 @@
override val sourceIdentity: Any = source
override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+ // Delay the calls to `source.setVisibility()` during the animation. This must be called
+ // before `GhostView.addGhost()` is called because the latter will change the *transition*
+ // visibility, which won't be blocked and will affect the normal View visibility that is
+ // saved by `setShouldBlockVisibilityChanges()` for a later restoration.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+
// Create a temporary ghost of the source (which will make it invisible) and add it
// to the host dialog.
GhostView.addGhost(source, viewGroup)
-
- // The ghost of the source was just created, so the source is currently invisible.
- // We need to make sure that it stays invisible as long as the dialog is shown or
- // animating.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
}
override fun stopDrawingInOverlay() {
// Note: here we should remove the ghost from the overlay, but in practice this is
- // already done by the launch controllers created below.
+ // already done by the launch controller created below.
- // Make sure we allow the source to change its visibility again.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
- source.visibility = View.VISIBLE
+ if (source is LaunchableView) {
+ // Make sure we allow the source to change its visibility again and restore its previous
+ // value.
+ source.setShouldBlockVisibilityChanges(false)
+ } else {
+ // We made the source invisible earlier, so let's make it visible again.
+ source.visibility = View.VISIBLE
+ }
}
override fun createLaunchController(): LaunchAnimator.Controller {
@@ -67,10 +73,14 @@
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
- // We hide the source when the dialog is showing. We will make this view
- // visible again when dismissing the dialog. This does nothing if the source
- // implements [LaunchableView], as it's already INVISIBLE in that case.
- source.visibility = View.INVISIBLE
+ // At this point the view visibility is restored by the delegate, so we delay the
+ // visibility changes again and make it invisible while the dialog is shown.
+ if (source is LaunchableView) {
+ source.setShouldBlockVisibilityChanges(true)
+ source.setTransitionVisibility(View.INVISIBLE)
+ } else {
+ source.visibility = View.INVISIBLE
+ }
}
}
}
@@ -90,13 +100,15 @@
}
override fun onExitAnimationCancelled() {
- // Make sure we allow the source to change its visibility again.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
- // If the view is invisible it's probably because of us, so we make it visible
- // again.
- if (source.visibility == View.INVISIBLE) {
- source.visibility = View.VISIBLE
+ if (source is LaunchableView) {
+ // Make sure we allow the source to change its visibility again.
+ source.setShouldBlockVisibilityChanges(false)
+ } else {
+ // If the view is invisible it's probably because of us, so we make it visible
+ // again.
+ if (source.visibility == View.INVISIBLE) {
+ source.visibility = View.VISIBLE
+ }
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index 259f0ed..4e96dda 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -41,6 +41,7 @@
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
@@ -73,6 +74,7 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import com.android.compose.runtime.movableContentOf
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.max
@@ -170,25 +172,25 @@
val contentColor = controller.contentColor
val shape = controller.shape
- // TODO(b/230830644): Use movableContentOf to preserve the content state instead once the
- // Compose libraries have been updated and include aosp/2163631.
val wrappedContent =
- @Composable { controller: ExpandableController ->
- CompositionLocalProvider(
- LocalContentColor provides contentColor,
- ) {
- // We make sure that the content itself (wrapped by the background) is at least
- // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
- // null, to make it easier to write expandables that are sometimes clickable and
- // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
- // the expandable is not clickable directly, then something in its content should
- // be (and with a size >= 40dp).
- val minSize = 40.dp
- Box(
- Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
- contentAlignment = Alignment.Center,
+ remember(content) {
+ movableContentOf { expandable: Expandable ->
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
) {
- content(controller.expandable)
+ // We make sure that the content itself (wrapped by the background) is at least
+ // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
+ // null, to make it easier to write expandables that are sometimes clickable and
+ // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
+ // the expandable is not clickable directly, then something in its content
+ // should be (and with a size >= 40dp).
+ val minSize = 40.dp
+ Box(
+ Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+ contentAlignment = Alignment.Center,
+ ) {
+ content(expandable)
+ }
}
}
}
@@ -270,7 +272,7 @@
.onGloballyPositioned {
controller.boundsInComposeViewRoot.value = it.boundsInRoot()
}
- ) { wrappedContent(controller) }
+ ) { wrappedContent(controller.expandable) }
}
else -> {
val clickModifier =
@@ -301,7 +303,7 @@
controller.boundsInComposeViewRoot.value = it.boundsInRoot()
},
) {
- wrappedContent(controller)
+ wrappedContent(controller.expandable)
}
}
}
@@ -315,7 +317,7 @@
animatorState: State<LaunchAnimator.State?>,
overlay: ViewGroupOverlay,
controller: ExpandableControllerImpl,
- content: @Composable (ExpandableController) -> Unit,
+ content: @Composable (Expandable) -> Unit,
composeViewRoot: View,
onOverlayComposeViewChanged: (View?) -> Unit,
density: Density,
@@ -370,7 +372,7 @@
// We center the content in the expanding container.
contentAlignment = Alignment.Center,
) {
- Box(contentModifier) { content(controller) }
+ Box(contentModifier) { content(controller.expandable) }
}
}
}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 6e728ce..e253fb9 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -17,13 +17,21 @@
package com.android.systemui.compose
+import android.content.Context
+import android.view.View
import androidx.activity.ComponentActivity
+import androidx.lifecycle.LifecycleOwner
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
/** The Compose facade, when Compose is *not* available. */
object ComposeFacade : BaseComposeFacade {
override fun isComposeAvailable(): Boolean = false
+ override fun composeInitializer(): ComposeInitializer {
+ throwComposeUnavailableError()
+ }
+
override fun setPeopleSpaceActivityContent(
activity: ComponentActivity,
viewModel: PeopleViewModel,
@@ -32,7 +40,15 @@
throwComposeUnavailableError()
}
- private fun throwComposeUnavailableError() {
+ override fun createFooterActionsView(
+ context: Context,
+ viewModel: FooterActionsViewModel,
+ qsVisibilityLifecycleOwner: LifecycleOwner
+ ): View {
+ throwComposeUnavailableError()
+ }
+
+ private fun throwComposeUnavailableError(): Nothing {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
" other function on ComposeFacade."
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 6991ff8..1ea18fe 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -16,16 +16,24 @@
package com.android.systemui.compose
+import android.content.Context
+import android.view.View
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.qs.footer.ui.compose.FooterActions
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
/** The Compose facade, when Compose is available. */
object ComposeFacade : BaseComposeFacade {
override fun isComposeAvailable(): Boolean = true
+ override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl
+
override fun setPeopleSpaceActivityContent(
activity: ComponentActivity,
viewModel: PeopleViewModel,
@@ -33,4 +41,14 @@
) {
activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
}
+
+ override fun createFooterActionsView(
+ context: Context,
+ viewModel: FooterActionsViewModel,
+ qsVisibilityLifecycleOwner: LifecycleOwner,
+ ): View {
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
new file mode 100644
index 0000000..772c891
--- /dev/null
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 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.compose
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewTreeLifecycleOwner
+import androidx.savedstate.SavedStateRegistry
+import androidx.savedstate.SavedStateRegistryController
+import androidx.savedstate.SavedStateRegistryOwner
+import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
+import com.android.systemui.lifecycle.ViewLifecycleOwner
+
+internal object ComposeInitializerImpl : ComposeInitializer {
+ override fun onAttachedToWindow(root: View) {
+ if (ViewTreeLifecycleOwner.get(root) != null) {
+ error("root $root already has a LifecycleOwner")
+ }
+
+ val parent = root.parent
+ if (parent is View && parent.id != android.R.id.content) {
+ error(
+ "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." +
+ "Outside of activities and dialogs, this is usually the top-most View of a " +
+ "window."
+ )
+ }
+
+ // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is
+ // both visible and focused.
+ val lifecycleOwner = ViewLifecycleOwner(root)
+
+ // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save
+ // or restore because SystemUI process is always running and top-level windows using this
+ // initializer are created once, when the process is started.
+ val savedStateRegistryOwner =
+ object : SavedStateRegistryOwner {
+ private val savedStateRegistry =
+ SavedStateRegistryController.create(this).apply { performRestore(null) }
+
+ override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
+
+ override fun getSavedStateRegistry(): SavedStateRegistry {
+ return savedStateRegistry.savedStateRegistry
+ }
+ }
+
+ // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
+ // because `onCreate` might move the lifecycle state to STARTED which will make
+ // [SavedStateRegistryController.performRestore] throw.
+ lifecycleOwner.onCreate()
+
+ // Set the owners on the root. They will be reused by any ComposeView inside the root
+ // hierarchy.
+ ViewTreeLifecycleOwner.set(root, lifecycleOwner)
+ ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
+ }
+
+ override fun onDetachedFromWindow(root: View) {
+ (ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy()
+ ViewTreeLifecycleOwner.set(root, null)
+ ViewTreeSavedStateRegistryOwner.set(root, null)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
new file mode 100644
index 0000000..9eb78e1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.compose.modifiers
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTagsAsResourceId
+
+/**
+ * Set a test tag on this node so that it is associated with [resId]. This node will then be
+ * accessible by integration tests using `sysuiResSelector(resId)`.
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+fun Modifier.sysuiResTag(resId: String): Modifier {
+ return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId")
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index 23dacf9..3eeadae 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -51,6 +51,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.R
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
@@ -110,7 +111,9 @@
recentTiles: List<PeopleTileViewModel>,
onTileClicked: (PeopleTileViewModel) -> Unit,
) {
- Column {
+ Column(
+ Modifier.sysuiResTag("top_level_with_conversations"),
+ ) {
Column(
Modifier.fillMaxWidth().padding(PeopleSpacePadding),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -132,7 +135,7 @@
}
LazyColumn(
- Modifier.fillMaxWidth(),
+ Modifier.fillMaxWidth().sysuiResTag("scroll_view"),
contentPadding =
PaddingValues(
top = 16.dp,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5c5ceef..349f5c3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -73,6 +73,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
@@ -180,9 +181,9 @@
security?.let { SecurityButton(it, Modifier.weight(1f)) }
foregroundServices?.let { ForegroundServicesButton(it) }
- userSwitcher?.let { IconButton(it) }
- IconButton(viewModel.settings)
- viewModel.power?.let { IconButton(it) }
+ userSwitcher?.let { IconButton(it, Modifier.sysuiResTag("multi_user_switch")) }
+ IconButton(viewModel.settings, Modifier.sysuiResTag("settings_button_container"))
+ viewModel.power?.let { IconButton(it, Modifier.sysuiResTag("pm_lite")) }
}
}
}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a4ee62c..10bb00c 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -24,12 +24,44 @@
# TODO(b/264686688): Handle these cases with more targeted annotations.
-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
private com.android.keyguard.KeyguardUpdateMonitorCallback *;
+ private com.android.systemui.privacy.PrivacyConfig$Callback *;
private com.android.systemui.privacy.PrivacyItemController$Callback *;
private com.android.systemui.settings.UserTracker$Callback *;
private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
private com.android.systemui.util.service.Observer$Callback *;
private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
}
+# Note that these rules are temporary companions to the above rules, required
+# for cases like Kotlin where fields with anonymous types use the anonymous type
+# rather than the supertype.
+-if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ <1> *;
+}
+-if class * extends com.android.systemui.privacy.PrivacyConfig$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ <1> *;
+}
+-if class * extends com.android.systemui.privacy.PrivacyItemController$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ <1> *;
+}
+-if class * extends com.android.systemui.settings.UserTracker$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ <1> *;
+}
+-if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ <1> *;
+}
+-if class * extends com.android.systemui.util.service.Observer$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ <1> *;
+}
+-if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+ <1> *;
+}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
index 4da47af..9fea32c 100644
--- a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
@@ -1,27 +1,22 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Copyright (C) 2022 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>
- <shape android:shape="rectangle">
- <solid android:color="@color/accessibility_magnifier_bg" />
- <corners android:radius="24dp" />
- <stroke
- android:color="@color/accessibility_magnifier_bg_stroke"
- android:width="1dp" />
- </shape>
- </item>
-</layer-list>
\ No newline at end of file
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 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">
+ <corners android:radius="@dimen/magnification_setting_background_corner_radius" />
+ <solid android:color="?androidprv:attr/colorSurface" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_done_bg.xml
similarity index 65%
rename from packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
rename to packages/SystemUI/res/drawable/accessibility_window_magnification_button_done_bg.xml
index eefe364..5c2bf3e 100644
--- a/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_done_bg.xml
@@ -14,13 +14,13 @@
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid android:color="@color/accessibility_window_magnifier_button_bg" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
<size
- android:width="40dp"
- android:height="40dp"/>
- <corners android:radius="2dp"/>
+ android:width="@dimen/magnification_setting_button_done_width"
+ android:height="@dimen/magnification_setting_button_done_height"/>
+ <corners android:radius="@dimen/magnification_setting_button_done_corner_radius"/>
<stroke
- android:color="@color/accessibility_window_magnifier_button_bg_stroke"
+ android:color="?androidprv:attr/colorAccent"
android:width="1dp" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index a3c0554..714d551 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -16,12 +16,13 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/magnifier_panel_view"
- android:layout_width="@dimen/magnification_max_size"
- android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:background="@drawable/accessibility_magnification_setting_view_bg"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:padding="@dimen/magnification_setting_background_padding">
<LinearLayout
- android:layout_width="@dimen/magnification_max_size"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
@@ -29,27 +30,23 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/accessibility_magnifier_size"
- android:textAppearance="?android:attr/textAppearanceListItem"
- android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
android:focusable="true"
- android:layout_gravity="center_vertical|left"
- android:layout_marginStart="20dp"/>
+ android:layout_gravity="center_vertical|left" />
<Button
android:id="@+id/magnifier_edit_button"
- android:background="@drawable/accessibility_magnification_setting_view_btn_bg"
+ android:background="@null"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/accessibility_magnifier_edit"
- android:textAppearance="?android:attr/textAppearanceListItem"
- android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:textAppearance="@style/TextAppearance.MagnificationSetting.EditButton"
android:focusable="true"
- android:layout_gravity="right"
- android:layout_marginEnd="20dp"/>
+ android:layout_gravity="right" />
</LinearLayout>
<LinearLayout
- android:layout_width="@dimen/magnification_max_size"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton
@@ -58,7 +55,6 @@
android:layout_height="56dp"
android:scaleType="center"
android:layout_weight="1"
- android:layout_marginStart="12dp"
android:background="@drawable/accessibility_magnification_setting_view_btn_bg"
android:padding="@dimen/magnification_switch_button_padding"
android:src="@drawable/ic_magnification_menu_small"
@@ -95,7 +91,6 @@
android:layout_height="56dp"
android:scaleType="center"
android:layout_weight="1"
- android:layout_marginEnd="12dp"
android:background="@drawable/accessibility_magnification_setting_view_btn_bg"
android:padding="@dimen/magnification_switch_button_padding"
android:src="@drawable/ic_open_in_new_fullscreen"
@@ -107,68 +102,53 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:paddingTop="8dp"
- android:paddingEnd="20dp"
- android:paddingStart="20dp"
+ android:layout_marginTop="@dimen/magnification_setting_view_margin"
+ android:layout_marginBottom="@dimen/magnification_setting_view_margin"
android:focusable="true">
- <LinearLayout
+ <TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:background="?android:attr/selectableItemBackground"
- android:ellipsize="marquee"
- android:gravity="center_vertical"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:orientation="vertical">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:text="@string/accessibility_allow_diagonal_scrolling"
- android:textAppearance="?android:attr/textAppearanceListItem"
- android:textColor="?android:attr/textColorAlertDialogListItem" />
- </LinearLayout>
+ android:singleLine="true"
+ android:text="@string/accessibility_allow_diagonal_scrolling"
+ android:textAppearance="@style/TextAppearance.MagnificationSetting.Title" />
<Switch
android:id="@+id/magnifier_horizontal_lock_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="right|center"
- android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+ android:layout_gravity="right" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/accessibility_magnification_zoom"
- android:textAppearance="?android:attr/textAppearanceListItem"
- android:textColor="?android:attr/textColorAlertDialogListItem"
- android:focusable="true"
- android:layout_marginStart="20dp"
- android:paddingTop="2dp"
- android:paddingBottom="10dp"/>
+ android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
+ android:focusable="true" />
<SeekBar
android:id="@+id/magnifier_zoom_seekbar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
+ android:layout_marginTop="@dimen/magnification_setting_seekbar_margin"
android:progress="0"
- android:max="6"
- android:layout_marginEnd="20dp"
- android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
-
+ android:max="6" />
<Button
- android:id="@+id/magnifier_close_button"
- android:background="@drawable/accessibility_magnification_setting_view_btn_bg"
- android:layout_width="wrap_content"
+ android:id="@+id/magnifier_done_button"
+ android:background="@drawable/accessibility_window_magnification_button_done_bg"
+ android:minHeight="@dimen/magnification_setting_button_done_height"
+ android:layout_width="@dimen/magnification_setting_button_done_width"
android:layout_height="wrap_content"
- android:text="@string/accessibility_magnification_close"
- android:textAppearance="?android:attr/textAppearanceListItem"
- android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:text="@string/accessibility_magnification_done"
+ android:textAppearance="@style/TextAppearance.MagnificationSetting.DoneButton"
android:focusable="true"
android:layout_gravity="center_horizontal"
- android:paddingBottom="24dp"/>
+ android:layout_marginTop="@dimen/magnification_setting_view_margin"
+ android:paddingLeft="@dimen/magnification_setting_button_done_padding_horizontal"
+ android:paddingRight="@dimen/magnification_setting_button_done_padding_horizontal"
+ android:paddingTop="@dimen/magnification_setting_button_done_padding_vertical"
+ android:paddingBottom="@dimen/magnification_setting_button_done_padding_vertical" />
</LinearLayout>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ba6977a..5bb96c4 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -175,9 +175,7 @@
<color name="accessibility_magnifier_bg">#FCFCFC</color>
<color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
<color name="accessibility_magnifier_icon_color">#252525</color>
- <color name="accessibility_window_magnifier_button_bg">#0680FD</color>
<color name="accessibility_window_magnifier_icon_color">#FAFAFA</color>
- <color name="accessibility_window_magnifier_button_bg_stroke">#252525</color>
<color name="accessibility_window_magnifier_corner_view_color">#0680FD</color>
<!-- Volume dialog colors -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6d5eb6a..fc67015 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1110,13 +1110,24 @@
<!-- The extra padding to show the whole outer border -->
<dimen name="magnifier_drag_handle_padding">3dp</dimen>
<dimen name="magnification_max_frame_size">300dp</dimen>
+ <!-- Magnification settings panel -->
+ <dimen name="magnification_setting_view_margin">24dp</dimen>
+ <dimen name="magnification_setting_text_size">18sp</dimen>
+ <dimen name="magnification_setting_background_padding">24dp</dimen>
+ <dimen name="magnification_setting_background_corner_radius">28dp</dimen>
+ <dimen name="magnification_setting_seekbar_margin">16dp</dimen>
+ <dimen name="magnification_setting_button_line_height">20sp</dimen>
+ <dimen name="magnification_setting_button_done_width">312dp</dimen>
+ <dimen name="magnification_setting_button_done_height">48dp</dimen>
+ <dimen name="magnification_setting_button_done_corner_radius">100dp</dimen>
+ <dimen name="magnification_setting_button_done_padding_vertical">10dp</dimen>
+ <dimen name="magnification_setting_button_done_padding_horizontal">24dp</dimen>
<!-- How far from the right edge of the screen you need to drag the window before the button
repositions to the other side. -->
<dimen name="magnification_button_reposition_threshold_from_edge">32dp</dimen>
<dimen name="magnification_drag_size">15dp</dimen>
- <dimen name="magnification_max_size">360dp</dimen>
<dimen name="magnifier_panel_size">265dp</dimen>
<!-- Home Controls -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2745202..4a89bb4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2289,9 +2289,9 @@
<string name="accessibility_magnification_small">Small</string>
<!-- Click action label for magnification panel large size [CHAR LIMIT=NONE]-->
<string name="accessibility_magnification_large">Large</string>
- <!-- Click action label for magnification panel Close [CHAR LIMIT=NONE]-->
- <string name="accessibility_magnification_close">Close</string>
- <!-- Click action label for edit magnification size [CHAR LIMIT=NONE]-->
+ <!-- Click action label for magnification panel Done [CHAR LIMIT=20]-->
+ <string name="accessibility_magnification_done">Done</string>
+ <!-- Click action label for edit magnification size [CHAR LIMIT=20]-->
<string name="accessibility_magnifier_edit">Edit</string>
<!-- Click action label for magnification panel settings [CHAR LIMIT=NONE]-->
<string name="accessibility_magnification_magnifier_window_settings">Magnifier window settings</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index aafa47f..f8f5e83 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1334,4 +1334,29 @@
<item name="biometricsEnrollProgressHelp">@color/udfps_enroll_progress_help</item>
<item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
</style>
+
+ <!-- Magnification styles -->
+ <style name="TextAppearance.MagnificationSetting" />
+
+ <style name="TextAppearance.MagnificationSetting.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:textColor">?androidprv:attr/textColorPrimary</item>
+ <item name="android:textSize">@dimen/magnification_setting_text_size</item>
+ </style>
+
+ <style name="TextAppearance.MagnificationSetting.EditButton">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:textColor">?androidprv:attr/colorAccent</item>
+ <item name="android:textSize">@dimen/magnification_setting_text_size</item>
+ <item name="android:lineHeight">@dimen/magnification_setting_button_line_height</item>
+ <item name="android:textAlignment">center</item>
+ </style>
+
+ <style name="TextAppearance.MagnificationSetting.DoneButton">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:textColor">?androidprv:attr/textColorPrimary</item>
+ <item name="android:textSize">@dimen/magnification_setting_text_size</item>
+ <item name="android:lineHeight">@dimen/magnification_setting_button_line_height</item>
+ <item name="android:textAlignment">center</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
index 7f2933e..c9e57b4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
@@ -15,21 +15,51 @@
package com.android.systemui.unfold.system
import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration
+import android.os.Trace
+import com.android.systemui.shared.system.TaskStackChangeListener
+import com.android.systemui.shared.system.TaskStackChangeListeners
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class ActivityManagerActivityTypeProvider @Inject constructor(
- private val activityManager: ActivityManager
-) : CurrentActivityTypeProvider {
+class ActivityManagerActivityTypeProvider
+@Inject
+constructor(private val activityManager: ActivityManager) : CurrentActivityTypeProvider {
override val isHomeActivity: Boolean?
- get() {
- val activityType = activityManager.getRunningTasks(/* maxNum= */ 1)
- ?.getOrNull(0)?.topActivityType ?: return null
+ get() = _isHomeActivity
- return activityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+ private var _isHomeActivity: Boolean? = null
+
+
+ override fun init() {
+ _isHomeActivity = activityManager.isOnHomeActivity()
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
+ }
+
+ override fun uninit() {
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
+ }
+
+ private val taskStackChangeListener =
+ object : TaskStackChangeListener {
+ override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+ _isHomeActivity = taskInfo.isHomeActivity()
+ }
}
+
+ private fun RunningTaskInfo.isHomeActivity(): Boolean =
+ topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+
+ private fun ActivityManager.isOnHomeActivity(): Boolean? {
+ try {
+ Trace.beginSection("isOnHomeActivity")
+ return getRunningTasks(/* maxNum= */ 1)?.firstOrNull()?.isHomeActivity()
+ } finally {
+ Trace.endSection()
+ }
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index 24ae42a..fe607e1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
@@ -56,6 +56,6 @@
abstract fun mainHandler(@Main handler: Handler): Handler
@Binds
- @UnfoldBackground
+ @UnfoldSingleThreadBg
abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 4f03b63..9537ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -89,6 +89,7 @@
protected WindowMagnificationController createInstance(Display display) {
final Context windowContext = mContext.createWindowContext(display,
TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
+ windowContext.setTheme(com.android.systemui.R.style.Theme_SystemUI);
return new WindowMagnificationController(
windowContext,
mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 9f857a8..56602ad 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -85,7 +85,7 @@
private ImageButton mSmallButton;
private ImageButton mMediumButton;
private ImageButton mLargeButton;
- private Button mCloseButton;
+ private Button mDoneButton;
private Button mEditButton;
private ImageButton mChangeModeButton;
private boolean mAllowDiagonalScrolling = false;
@@ -160,9 +160,9 @@
} else if (viewId == R.id.magnifier_large_button) {
return mContext.getResources().getString(
R.string.accessibility_magnification_large);
- } else if (viewId == R.id.magnifier_close_button) {
+ } else if (viewId == R.id.magnifier_done_button) {
return mContext.getResources().getString(
- R.string.accessibility_magnification_close);
+ R.string.accessibility_magnification_done);
} else if (viewId == R.id.magnifier_edit_button) {
return mContext.getResources().getString(
R.string.accessibility_resize);
@@ -247,7 +247,7 @@
setMagnifierSize(MagnificationSize.LARGE);
} else if (id == R.id.magnifier_edit_button) {
editMagnifierSizeMode(true);
- } else if (id == R.id.magnifier_close_button) {
+ } else if (id == R.id.magnifier_done_button) {
hideSettingPanel();
} else if (id == R.id.magnifier_full_button) {
hideSettingPanel();
@@ -381,7 +381,7 @@
mSmallButton = mSettingView.findViewById(R.id.magnifier_small_button);
mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
- mCloseButton = mSettingView.findViewById(R.id.magnifier_close_button);
+ mDoneButton = mSettingView.findViewById(R.id.magnifier_done_button);
mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
@@ -408,8 +408,8 @@
mLargeButton.setAccessibilityDelegate(mButtonDelegate);
mLargeButton.setOnClickListener(mButtonClickListener);
- mCloseButton.setAccessibilityDelegate(mButtonDelegate);
- mCloseButton.setOnClickListener(mButtonClickListener);
+ mDoneButton.setAccessibilityDelegate(mButtonDelegate);
+ mDoneButton.setOnClickListener(mButtonClickListener);
mChangeModeButton.setAccessibilityDelegate(mButtonDelegate);
mChangeModeButton.setOnClickListener(mButtonClickListener);
@@ -428,7 +428,8 @@
}
void onConfigurationChanged(int configDiff) {
- if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+ if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0
+ || (configDiff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
boolean showSettingPanelAfterThemeChange = mIsVisible;
hideSettingPanel(/* resetPosition= */ false);
inflateView();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 199e630..79c09fd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -239,6 +239,10 @@
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("mSensorProps=(" + mSensorProps + ")");
+ pw.println("Using new touch detection framework: " + mFeatureFlags.isEnabled(
+ Flags.UDFPS_NEW_TOUCH_DETECTION));
+ pw.println("Using ellipse touch detection: " + mFeatureFlags.isEnabled(
+ Flags.UDFPS_ELLIPSE_DETECTION));
}
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
index aa60522..28bc2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
@@ -26,28 +26,28 @@
* Value obtained from [MotionEvent.getPointerId], or [MotionEvent.INVALID_POINTER_ID] if the ID
* is not available.
*/
- val pointerId: Int,
+ val pointerId: Int = MotionEvent.INVALID_POINTER_ID,
/** [MotionEvent.getRawX] mapped to natural orientation and native resolution. */
- val x: Float,
+ val x: Float = 0f,
/** [MotionEvent.getRawY] mapped to natural orientation and native resolution. */
- val y: Float,
+ val y: Float = 0f,
/** [MotionEvent.getTouchMinor] mapped to natural orientation and native resolution. */
- val minor: Float,
+ val minor: Float = 0f,
/** [MotionEvent.getTouchMajor] mapped to natural orientation and native resolution. */
- val major: Float,
+ val major: Float = 0f,
/** [MotionEvent.getOrientation] mapped to natural orientation. */
- val orientation: Float,
+ val orientation: Float = 0f,
/** [MotionEvent.getEventTime]. */
- val time: Long,
+ val time: Long = 0,
/** [MotionEvent.getDownTime]. */
- val gestureStart: Long,
+ val gestureStart: Long = 0,
) {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 693f64a..3a01cd5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -43,74 +43,72 @@
): TouchProcessorResult {
fun preprocess(): PreprocessedTouch {
- // TODO(b/253085297): Add multitouch support. pointerIndex can be > 0 for ACTION_MOVE.
- val pointerIndex = 0
- val touchData = event.normalize(pointerIndex, overlayParams)
- val isGoodOverlap =
- overlapDetector.isGoodOverlap(touchData, overlayParams.nativeSensorBounds)
- return PreprocessedTouch(touchData, previousPointerOnSensorId, isGoodOverlap)
+ val touchData = List(event.pointerCount) { event.normalize(it, overlayParams) }
+ val pointersOnSensor =
+ touchData
+ .filter { overlapDetector.isGoodOverlap(it, overlayParams.nativeSensorBounds) }
+ .map { it.pointerId }
+ return PreprocessedTouch(touchData, previousPointerOnSensorId, pointersOnSensor)
}
return when (event.actionMasked) {
- MotionEvent.ACTION_DOWN -> processActionDown(preprocess())
+ MotionEvent.ACTION_DOWN,
+ MotionEvent.ACTION_POINTER_DOWN,
MotionEvent.ACTION_MOVE -> processActionMove(preprocess())
- MotionEvent.ACTION_UP -> processActionUp(preprocess())
- MotionEvent.ACTION_CANCEL ->
- processActionCancel(event.normalize(pointerIndex = 0, overlayParams))
+ MotionEvent.ACTION_UP,
+ MotionEvent.ACTION_POINTER_UP ->
+ processActionUp(preprocess(), event.getPointerId(event.actionIndex))
+ MotionEvent.ACTION_CANCEL -> processActionCancel(NormalizedTouchData())
else ->
Failure("Unsupported MotionEvent." + MotionEvent.actionToString(event.actionMasked))
}
}
}
+/**
+ * [data] contains a list of NormalizedTouchData for pointers in the motionEvent ordered by
+ * pointerIndex
+ *
+ * [previousPointerOnSensorId] the pointerId of the previous pointer on the sensor,
+ * [MotionEvent.INVALID_POINTER_ID] if none
+ *
+ * [pointersOnSensor] contains a list of ids of pointers on the sensor
+ */
private data class PreprocessedTouch(
- val data: NormalizedTouchData,
+ val data: List<NormalizedTouchData>,
val previousPointerOnSensorId: Int,
- val isGoodOverlap: Boolean,
+ val pointersOnSensor: List<Int>,
)
-private fun processActionDown(touch: PreprocessedTouch): TouchProcessorResult {
- return if (touch.isGoodOverlap) {
- ProcessedTouch(InteractionEvent.DOWN, pointerOnSensorId = touch.data.pointerId, touch.data)
- } else {
- val event =
- if (touch.data.pointerId == touch.previousPointerOnSensorId) {
- InteractionEvent.UP
- } else {
- InteractionEvent.UNCHANGED
- }
- ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
- }
-}
-
private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult {
val hadPointerOnSensor = touch.previousPointerOnSensorId != INVALID_POINTER_ID
- val interactionEvent =
- when {
- touch.isGoodOverlap && !hadPointerOnSensor -> InteractionEvent.DOWN
- !touch.isGoodOverlap && hadPointerOnSensor -> InteractionEvent.UP
- else -> InteractionEvent.UNCHANGED
- }
- val pointerOnSensorId =
- when (interactionEvent) {
- InteractionEvent.UNCHANGED -> touch.previousPointerOnSensorId
- InteractionEvent.DOWN -> touch.data.pointerId
- else -> INVALID_POINTER_ID
- }
- return ProcessedTouch(interactionEvent, pointerOnSensorId, touch.data)
+ val hasPointerOnSensor = touch.pointersOnSensor.isNotEmpty()
+ val pointerOnSensorId = touch.pointersOnSensor.firstOrNull() ?: INVALID_POINTER_ID
+
+ return if (!hadPointerOnSensor && hasPointerOnSensor) {
+ val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
+ ProcessedTouch(InteractionEvent.DOWN, data.pointerId, data)
+ } else if (hadPointerOnSensor && !hasPointerOnSensor) {
+ ProcessedTouch(InteractionEvent.UP, INVALID_POINTER_ID, NormalizedTouchData())
+ } else {
+ val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
+ ProcessedTouch(InteractionEvent.UNCHANGED, pointerOnSensorId, data)
+ }
}
-private fun processActionUp(touch: PreprocessedTouch): TouchProcessorResult {
- return if (touch.isGoodOverlap) {
- ProcessedTouch(InteractionEvent.UP, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+private fun processActionUp(touch: PreprocessedTouch, actionId: Int): TouchProcessorResult {
+ // Finger lifted and it was the only finger on the sensor
+ return if (touch.pointersOnSensor.size == 1 && touch.pointersOnSensor.contains(actionId)) {
+ ProcessedTouch(
+ InteractionEvent.UP,
+ pointerOnSensorId = INVALID_POINTER_ID,
+ NormalizedTouchData()
+ )
} else {
- val event =
- if (touch.previousPointerOnSensorId != INVALID_POINTER_ID) {
- InteractionEvent.UP
- } else {
- InteractionEvent.UNCHANGED
- }
- ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+ // Pick new pointerOnSensor that's not the finger that was lifted
+ val pointerOnSensorId = touch.pointersOnSensor.find { it != actionId } ?: INVALID_POINTER_ID
+ val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
+ ProcessedTouch(InteractionEvent.UNCHANGED, data.pointerId, data)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
index f95a8ee..7bbfec7 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
@@ -28,7 +28,6 @@
LaunchableViewDelegate(
this,
superSetVisibility = { super.setVisibility(it) },
- superSetTransitionVisibility = { super.setTransitionVisibility(it) },
)
constructor(context: Context?) : super(context)
@@ -53,8 +52,4 @@
override fun setVisibility(visibility: Int) {
delegate.setVisibility(visibility)
}
-
- override fun setTransitionVisibility(visibility: Int) {
- delegate.setTransitionVisibility(visibility)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
index c27b82a..ddde628 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
@@ -28,7 +28,6 @@
LaunchableViewDelegate(
this,
superSetVisibility = { super.setVisibility(it) },
- superSetTransitionVisibility = { super.setTransitionVisibility(it) },
)
constructor(context: Context?) : super(context)
@@ -53,8 +52,4 @@
override fun setVisibility(visibility: Int) {
delegate.setVisibility(visibility)
}
-
- override fun setTransitionVisibility(visibility: Int) {
- delegate.setTransitionVisibility(visibility)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index e5ec727..c0f8549 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -17,8 +17,12 @@
package com.android.systemui.compose
+import android.content.Context
+import android.view.View
import androidx.activity.ComponentActivity
+import androidx.lifecycle.LifecycleOwner
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
/**
* A facade to interact with Compose, when it is available.
@@ -35,10 +39,22 @@
*/
fun isComposeAvailable(): Boolean
+ /**
+ * Return the [ComposeInitializer] to make Compose usable in windows outside normal activities.
+ */
+ fun composeInitializer(): ComposeInitializer
+
/** Bind the content of [activity] to [viewModel]. */
fun setPeopleSpaceActivityContent(
activity: ComponentActivity,
viewModel: PeopleViewModel,
onResult: (PeopleViewModel.Result) -> Unit,
)
+
+ /** Create a [View] to represent [viewModel] on screen. */
+ fun createFooterActionsView(
+ context: Context,
+ viewModel: FooterActionsViewModel,
+ qsVisibilityLifecycleOwner: LifecycleOwner,
+ ): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt
new file mode 100644
index 0000000..90dc3a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.compose
+
+import android.view.View
+
+/**
+ * An initializer to use Compose outside of an Activity, e.g. inside a window added directly using
+ * [android.view.WindowManager.addView] (like the shade or status bar) or inside a dialog.
+ *
+ * Example:
+ * ```
+ * windowManager.addView(MyWindowRootView(context), /* layoutParams */)
+ *
+ * class MyWindowRootView(context: Context) : FrameLayout(context) {
+ * override fun onAttachedToWindow() {
+ * super.onAttachedToWindow()
+ * ComposeInitializer.onAttachedToWindow(this)
+ * }
+ *
+ * override fun onDetachedFromWindow() {
+ * super.onDetachedFromWindow()
+ * ComposeInitializer.onDetachedFromWindow(this)
+ * }
+ * }
+ * ```
+ */
+interface ComposeInitializer {
+ /** Function to be called on your window root view's [View.onAttachedToWindow] function. */
+ fun onAttachedToWindow(root: View)
+
+ /** Function to be called on your window root view's [View.onDetachedFromWindow] function. */
+ fun onDetachedFromWindow(root: View)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c880c59..a5d9fee 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -75,9 +75,7 @@
unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true)
// TODO(b/254512731): Tracking Bug
- @JvmField
- val NOTIFICATION_DISMISSAL_FADE =
- unreleasedFlag(113, "notification_dismissal_fade", teamfood = true)
+ @JvmField val NOTIFICATION_DISMISSAL_FADE = releasedFlag(113, "notification_dismissal_fade")
// TODO(b/259558771): Tracking Bug
val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
@@ -210,6 +208,12 @@
// TODO(b/262859270): Tracking Bug
@JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
+ /** Enables code to show contextual loyalty cards in wallet entrypoints */
+ // TODO(b/247587924): Tracking Bug
+ @JvmField
+ val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS =
+ unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -291,7 +295,7 @@
// 801 - region sampling
// TODO(b/254512848): Tracking Bug
- val REGION_SAMPLING = unreleasedFlag(801, "region_sampling", teamfood = true)
+ val REGION_SAMPLING = unreleasedFlag(801, "region_sampling")
// 803 - screen contents translation
// TODO(b/254513187): Tracking Bug
@@ -407,6 +411,17 @@
val WM_DESKTOP_WINDOWING_2 =
sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+ // TODO(b/254513207): Tracking Bug to delete
+ @Keep
+ @JvmField
+ val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES =
+ unreleasedFlag(
+ 1113,
+ name = "screen_record_enterprise_policies",
+ namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ teamfood = false
+ )
+
// 1200 - predictive back
@Keep
@JvmField
@@ -498,6 +513,7 @@
@JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
@JvmField
val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications")
+ @JvmField val ENABLE_STYLUS_EDUCATION = unreleasedFlag(2303, "enable_stylus_education")
// 2400 - performance tools and debugging info
// TODO(b/238923086): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index e364918..d69ac7f 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -145,7 +145,7 @@
* └───────────────┴───────────────────┴──────────────┴─────────────────┘
* ```
*/
-private class ViewLifecycleOwner(
+class ViewLifecycleOwner(
private val view: View,
) : LifecycleOwner {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index ceb4845..a692ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -18,6 +18,7 @@
import android.app.ActivityOptions
import android.content.Intent
import android.content.res.Configuration
+import android.content.res.Resources
import android.media.projection.IMediaProjection
import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
import android.os.Binder
@@ -27,6 +28,7 @@
import android.os.UserHandle
import android.view.ViewGroup
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
import com.android.internal.app.ChooserActivity
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
@@ -59,16 +61,12 @@
private lateinit var configurationController: ConfigurationController
private lateinit var controller: MediaProjectionAppSelectorController
private lateinit var recentsViewController: MediaProjectionRecentsViewController
+ private lateinit var component: MediaProjectionAppSelectorComponent
override fun getLayoutResource() = R.layout.media_projection_app_selector
public override fun onCreate(bundle: Bundle?) {
- val component =
- componentFactory.create(
- activity = this,
- view = this,
- resultHandler = this
- )
+ component = componentFactory.create(activity = this, view = this, resultHandler = this)
// Create a separate configuration controller for this activity as the configuration
// might be different from the global one
@@ -76,11 +74,12 @@
controller = component.controller
recentsViewController = component.recentsViewController
- val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
- intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
+ intent.configureChooserIntent(
+ resources,
+ component.hostUserHandle,
+ component.personalProfileUserHandle
+ )
- val title = getString(R.string.media_projection_permission_app_selector_title)
- intent.putExtra(Intent.EXTRA_TITLE, title)
super.onCreate(bundle)
controller.init()
}
@@ -183,6 +182,13 @@
override fun shouldShowContentPreview() = true
+ override fun shouldShowContentPreviewWhenEmpty(): Boolean = true
+
+ override fun createMyUserIdProvider(): MyUserIdProvider =
+ object : MyUserIdProvider() {
+ override fun getMyUserId(): Int = component.hostUserHandle.identifier
+ }
+
override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
recentsViewController.createView(parent)
@@ -193,6 +199,34 @@
* instance through activity result.
*/
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
+
+ /** UID of the app that originally launched the media projection flow (host app user) */
+ const val EXTRA_HOST_APP_USER_HANDLE = "launched_from_user_handle"
const val KEY_CAPTURE_TARGET = "capture_region"
+
+ /** Set up intent for the [ChooserActivity] */
+ private fun Intent.configureChooserIntent(
+ resources: Resources,
+ hostUserHandle: UserHandle,
+ personalProfileUserHandle: UserHandle
+ ) {
+ // Specify the query intent to show icons for all apps on the chooser screen
+ val queryIntent =
+ Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
+ putExtra(Intent.EXTRA_INTENT, queryIntent)
+
+ // Update the title of the chooser
+ val title = resources.getString(R.string.media_projection_permission_app_selector_title)
+ putExtra(Intent.EXTRA_TITLE, title)
+
+ // Select host app's profile tab by default
+ val selectedProfile =
+ if (hostUserHandle == personalProfileUserHandle) {
+ PROFILE_PERSONAL
+ } else {
+ PROFILE_WORK
+ }
+ putExtra(EXTRA_SELECTED_PROFILE, selectedProfile)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index bfa67a8..d830fc4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -22,6 +22,7 @@
import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
@@ -35,6 +36,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.text.BidiFormatter;
import android.text.SpannableString;
import android.text.TextPaint;
@@ -208,8 +210,14 @@
final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
projection.asBinder());
+ intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
+ UserHandle.getUserHandleForUid(getLaunchedFromUid()));
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
- startActivity(intent);
+
+ // Start activity from the current foreground user to avoid creating a separate
+ // SystemUI process without access to recent tasks because it won't have
+ // WM Shell running inside.
+ startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
}
} catch (RemoteException e) {
Log.e(TAG, "Error granting projection permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 9f28d46..6a5e725 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -864,7 +864,7 @@
notificationKey = key,
hasCheckedForResume = hasCheckedForResume,
isPlaying = isPlaying,
- isClearable = sbn.isClearable(),
+ isClearable = !sbn.isOngoing,
lastActive = lastActive,
instanceId = instanceId,
appUid = appUid,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 6c41caa..1d86343 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -19,9 +19,11 @@
import android.app.Activity
import android.content.ComponentName
import android.content.Context
+import android.os.UserHandle
import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
@@ -30,6 +32,8 @@
import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Binds
@@ -39,6 +43,7 @@
import dagger.Subcomponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import java.lang.IllegalArgumentException
import javax.inject.Qualifier
import javax.inject.Scope
import kotlinx.coroutines.CoroutineScope
@@ -46,6 +51,12 @@
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
+
@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
@@ -83,7 +94,7 @@
@MediaProjectionAppSelector
@MediaProjectionAppSelectorScope
fun provideAppSelectorComponentName(context: Context): ComponentName =
- ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
+ ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
@Provides
@MediaProjectionAppSelector
@@ -93,9 +104,32 @@
): ConfigurationController = ConfigurationControllerImpl(activity)
@Provides
- fun bindIconFactory(
- context: Context
- ): IconFactory = IconFactory.obtain(context)
+ @PersonalProfile
+ @MediaProjectionAppSelectorScope
+ fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
+ // Current foreground user is the 'personal' profile
+ return UserHandle.of(activityManagerWrapper.currentUserId)
+ }
+
+ @Provides
+ @WorkProfile
+ @MediaProjectionAppSelectorScope
+ fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
+ userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
+
+ @Provides
+ @HostUserHandle
+ @MediaProjectionAppSelectorScope
+ fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle {
+ val extras =
+ activity.intent.extras
+ ?: error("MediaProjectionAppSelectorActivity should be launched with extras")
+ return extras.getParcelable(EXTRA_HOST_APP_USER_HANDLE)
+ ?: error("MediaProjectionAppSelectorActivity should be provided with " +
+ "$EXTRA_HOST_APP_USER_HANDLE extra")
+ }
+
+ @Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context)
@Provides
@MediaProjectionAppSelector
@@ -124,6 +158,8 @@
val controller: MediaProjectionAppSelectorController
val recentsViewController: MediaProjectionRecentsViewController
+ @get:HostUserHandle val hostUserHandle: UserHandle
+ @get:PersonalProfile val personalProfileUserHandle: UserHandle
@MediaProjectionAppSelector val configurationController: ConfigurationController
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index d744a40b..52c7ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -17,24 +17,36 @@
package com.android.systemui.mediaprojection.appselector
import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
-import javax.inject.Inject
@MediaProjectionAppSelectorScope
-class MediaProjectionAppSelectorController @Inject constructor(
+class MediaProjectionAppSelectorController
+@Inject
+constructor(
private val recentTaskListProvider: RecentTaskListProvider,
private val view: MediaProjectionAppSelectorView,
+ private val flags: FeatureFlags,
+ @HostUserHandle private val hostUserHandle: UserHandle,
@MediaProjectionAppSelector private val scope: CoroutineScope,
@MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
) {
fun init() {
scope.launch {
- val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
+ val recentTasks = recentTaskListProvider.loadRecentTasks()
+
+ val tasks = recentTasks
+ .filterDevicePolicyRestrictedTasks()
+ .sortedTasks()
+
view.bind(tasks)
}
}
@@ -43,9 +55,20 @@
scope.cancel()
}
- private fun List<RecentTask>.sortTasks(): List<RecentTask> =
- sortedBy {
- // Show normal tasks first and only then tasks with opened app selector
- it.topActivityComponent == appSelectorComponentName
+ /**
+ * Removes all recent tasks that are different from the profile of the host app to avoid any
+ * cross-profile sharing
+ */
+ private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> =
+ if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+ // TODO(b/263950746): filter tasks based on the enterprise policies
+ this
+ } else {
+ filter { UserHandle.of(it.userId) == hostUserHandle }
}
+
+ private fun List<RecentTask>.sortedTasks(): List<RecentTask> = sortedBy {
+ // Show normal tasks first and only then tasks with opened app selector
+ it.topActivityComponent == appSelectorComponentName
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index cd994b8..41e2286 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -17,11 +17,12 @@
package com.android.systemui.mediaprojection.appselector.data
import android.annotation.ColorInt
+import android.annotation.UserIdInt
import android.content.ComponentName
data class RecentTask(
val taskId: Int,
- val userId: Int,
+ @UserIdInt val userId: Int,
val topActivityComponent: ComponentName?,
val baseIntentComponent: ComponentName?,
@ColorInt val colorBackground: Int?
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e3d9f6d..e0aa6a8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1031,6 +1031,9 @@
pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion);
mView.dump(pw);
mRegionSamplingHelper.dump(pw);
+ if (mAutoHideController != null) {
+ mAutoHideController.dump(pw);
+ }
}
// ----- CommandQueue Callbacks -----
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index fba5f63..7f0f894 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -68,8 +68,10 @@
};
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+ Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity");
ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
} else {
+ Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity");
ViewGroup view = PeopleViewBinder.create(this);
PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult);
setContentView(view);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 5ef7126..b673f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -48,6 +48,7 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.ui.MediaHost;
@@ -227,9 +228,7 @@
mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
this);
- LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions);
- FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
- mListeningAndVisibilityLifecycleOwner);
+ bindFooterActionsView(view);
mFooterActionsController.init();
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
@@ -290,6 +289,33 @@
});
}
+ private void bindFooterActionsView(View root) {
+ LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
+
+ if (!ComposeFacade.INSTANCE.isComposeAvailable()) {
+ Log.d(TAG, "Binding the View implementation of the QS footer actions");
+ FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
+ mListeningAndVisibilityLifecycleOwner);
+ return;
+ }
+
+ // Compose is available, so let's use the Compose implementation of the footer actions.
+ Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
+ View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(),
+ mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
+
+ // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin
+ // to all views except for qs_footer_actions, so we set it to the Compose view.
+ composeView.setId(R.id.qs_footer_actions);
+
+ // Replace the View by the Compose provided one.
+ ViewGroup parent = (ViewGroup) footerActionsView.getParent();
+ ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams();
+ int index = parent.indexOfChild(footerActionsView);
+ parent.removeViewAt(index);
+ parent.addView(composeView, index, layoutParams);
+ }
+
@Override
public void setScrollListener(ScrollListener listener) {
mScrollListener = listener;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 3d48fd1..84a18d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -132,7 +132,7 @@
final String slot = tile.getComponent().getClassName();
// TileServices doesn't know how to add more than 1 icon per slot, so remove all
mMainHandler.post(() -> mHost.getIconController()
- .removeAllIconsForSlot(slot));
+ .removeAllIconsForExternalSlot(slot));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index b355d4b..29d7fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -145,7 +145,6 @@
private val launchableViewDelegate = LaunchableViewDelegate(
this,
superSetVisibility = { super.setVisibility(it) },
- superSetTransitionVisibility = { super.setTransitionVisibility(it) },
)
private var lastDisabledByPolicy = false
@@ -362,10 +361,6 @@
launchableViewDelegate.setVisibility(visibility)
}
- override fun setTransitionVisibility(visibility: Int) {
- launchableViewDelegate.setTransitionVisibility(visibility)
- }
-
// Accessibility
override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 44b18ec..68e3dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -23,6 +23,7 @@
import android.os.Handler
import android.os.Looper
import android.os.ResultReceiver
+import android.os.UserHandle
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
@@ -77,6 +78,14 @@
MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
CaptureTargetResultReceiver()
)
+
+ // Send SystemUI's user handle as the host app user handle because SystemUI
+ // is the 'host app' (the app that receives screen capture data)
+ intent.putExtra(
+ MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
+ UserHandle.of(UserHandle.myUserId())
+ )
+
val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
if (animationController == null) {
dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 964d0b2..fd31e49 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -715,7 +715,7 @@
updatePanelExpansionAndVisibility();
};
private final Runnable mMaybeHideExpandedRunnable = () -> {
- if (getExpansionFraction() == 0.0f) {
+ if (getExpandedFraction() == 0.0f) {
postToView(mHideExpandedRunnable);
}
};
@@ -5451,10 +5451,6 @@
InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
- private float getExpansionFraction() {
- return mExpandedFraction;
- }
-
private ShadeExpansionStateManager getShadeExpansionStateManager() {
return mShadeExpansionStateManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index ca03127..f712629 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -58,6 +58,7 @@
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import com.android.systemui.R;
+import com.android.systemui.compose.ComposeFacade;
/**
* Combined keyguard and notification panel view. Also holding backdrop and scrims.
@@ -149,6 +150,18 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setWillNotDraw(!DEBUG);
+
+ if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+ ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+ ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
index 662f70e..438b0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
@@ -36,10 +36,6 @@
visibility -> {
super.setVisibility(visibility);
return Unit.INSTANCE;
- },
- visibility -> {
- super.setTransitionVisibility(visibility);
- return Unit.INSTANCE;
});
public AlphaOptimizedFrameLayout(Context context) {
@@ -73,9 +69,4 @@
public void setVisibility(int visibility) {
mLaunchableViewDelegate.setVisibility(visibility);
}
-
- @Override
- public void setTransitionVisibility(int visibility) {
- mLaunchableViewDelegate.setTransitionVisibility(visibility);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 3ccef9d..eb81c46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -16,25 +16,35 @@
package com.android.systemui.statusbar.phone;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.IWindowManager;
import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.AutoHideUiElement;
+import java.io.PrintWriter;
+
import javax.inject.Inject;
/** A controller to control all auto-hide things. Also see {@link AutoHideUiElement}. */
@SysUISingleton
public class AutoHideController {
private static final String TAG = "AutoHideController";
- private static final long AUTO_HIDE_TIMEOUT_MS = 2250;
+ private static final int AUTO_HIDE_TIMEOUT_MS = 2250;
+ private static final int USER_AUTO_HIDE_TIMEOUT_MS = 350;
+ private final AccessibilityManager mAccessibilityManager;
private final IWindowManager mWindowManagerService;
private final Handler mHandler;
@@ -52,11 +62,12 @@
};
@Inject
- public AutoHideController(Context context, @Main Handler handler,
+ public AutoHideController(Context context,
+ @Main Handler handler,
IWindowManager iWindowManager) {
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mHandler = handler;
mWindowManagerService = iWindowManager;
-
mDisplayId = context.getDisplayId();
}
@@ -138,7 +149,12 @@
private void scheduleAutoHide() {
cancelAutoHide();
- mHandler.postDelayed(mAutoHide, AUTO_HIDE_TIMEOUT_MS);
+ mHandler.postDelayed(mAutoHide, getAutoHideTimeout());
+ }
+
+ private int getAutoHideTimeout() {
+ return mAccessibilityManager.getRecommendedTimeoutMillis(AUTO_HIDE_TIMEOUT_MS,
+ FLAG_CONTENT_CONTROLS);
}
public void checkUserAutoHide(MotionEvent event) {
@@ -160,7 +176,13 @@
private void userAutoHide() {
cancelAutoHide();
- mHandler.postDelayed(mAutoHide, 350); // longer than app gesture -> flag clear
+ // longer than app gesture -> flag clear
+ mHandler.postDelayed(mAutoHide, getUserAutoHideTimeout());
+ }
+
+ private int getUserAutoHideTimeout() {
+ return mAccessibilityManager.getRecommendedTimeoutMillis(USER_AUTO_HIDE_TIMEOUT_MS,
+ FLAG_CONTENT_CONTROLS);
}
private boolean isAnyTransientBarShown() {
@@ -175,6 +197,15 @@
return false;
}
+ public void dump(@NonNull PrintWriter pw) {
+ pw.println("AutoHideController:");
+ pw.println("\tmAutoHideSuspended=" + mAutoHideSuspended);
+ pw.println("\tisAnyTransientBarShown=" + isAnyTransientBarShown());
+ pw.println("\thasPendingAutoHide=" + mHandler.hasCallbacks(mAutoHide));
+ pw.println("\tgetAutoHideTimeout=" + getAutoHideTimeout());
+ pw.println("\tgetUserAutoHideTimeout=" + getUserAutoHideTimeout());
+ }
+
/**
* Injectable factory for creating a {@link AutoHideController}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b965ac9..ff1b31d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -30,6 +30,9 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.tuner.TunerService
import java.io.PrintWriter
@@ -40,11 +43,19 @@
private val mKeyguardStateController: KeyguardStateController
private val statusBarStateController: StatusBarStateController
+ private val devicePostureController: DevicePostureController
@BypassOverride private val bypassOverride: Int
private var hasFaceFeature: Boolean
+ @DevicePostureInt private val configFaceAuthSupportedPosture: Int
+ @DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN
private var pendingUnlock: PendingUnlock? = null
private val listeners = mutableListOf<OnBypassStateChangedListener>()
-
+ private val postureCallback = DevicePostureController.Callback { posture ->
+ if (postureState != posture) {
+ postureState = posture
+ notifyListeners()
+ }
+ }
private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
override fun onFaceAuthEnabledChanged() = notifyListeners()
}
@@ -86,7 +97,8 @@
FACE_UNLOCK_BYPASS_NEVER -> false
else -> field
}
- return enabled && mKeyguardStateController.isFaceAuthEnabled
+ return enabled && mKeyguardStateController.isFaceAuthEnabled &&
+ isPostureAllowedForFaceAuth()
}
private set(value) {
field = value
@@ -106,18 +118,31 @@
lockscreenUserManager: NotificationLockscreenUserManager,
keyguardStateController: KeyguardStateController,
shadeExpansionStateManager: ShadeExpansionStateManager,
+ devicePostureController: DevicePostureController,
dumpManager: DumpManager
) {
this.mKeyguardStateController = keyguardStateController
this.statusBarStateController = statusBarStateController
+ this.devicePostureController = devicePostureController
bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
+ configFaceAuthSupportedPosture =
+ context.resources.getInteger(R.integer.config_face_auth_supported_posture)
- hasFaceFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)
+ hasFaceFeature = context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
if (!hasFaceFeature) {
return
}
+ if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
+ devicePostureController.addCallback { posture ->
+ if (postureState != posture) {
+ postureState = posture
+ notifyListeners()
+ }
+ }
+ }
+
dumpManager.registerDumpable("KeyguardBypassController", this)
statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
override fun onStateChanged(newState: Int) {
@@ -203,6 +228,13 @@
pendingUnlock = null
}
+ fun isPostureAllowedForFaceAuth(): Boolean {
+ return when (configFaceAuthSupportedPosture) {
+ DEVICE_POSTURE_UNKNOWN -> true
+ else -> (postureState == configFaceAuthSupportedPosture)
+ }
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("KeyguardBypassController:")
if (pendingUnlock != null) {
@@ -219,6 +251,7 @@
pw.println(" launchingAffordance: $launchingAffordance")
pw.println(" qSExpanded: $qsExpanded")
pw.println(" hasFaceFeature: $hasFaceFeature")
+ pw.println(" postureState: $postureState")
}
/** Registers a listener for bypass state changes. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 1a14a036..24ad55d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -79,12 +79,30 @@
/** Refresh the state of an IconManager by recreating the views */
void refreshIconGroup(IconManager iconManager);
- /** */
+
+ /**
+ * Adds or updates an icon for a given slot for a **tile service icon**.
+ *
+ * TODO(b/265307726): Merge with {@link #setIcon(String, StatusBarIcon)} or make this method
+ * much more clearly distinct from that method.
+ */
void setExternalIcon(String slot);
- /** */
+
+ /**
+ * Adds or updates an icon for the given slot for **internal system icons**.
+ *
+ * TODO(b/265307726): Rename to `setInternalIcon`, or merge this appropriately with the
+ * {@link #setIcon(String, StatusBarIcon)} method.
+ */
void setIcon(String slot, int resourceId, CharSequence contentDescription);
- /** */
+
+ /**
+ * Adds or updates an icon for the given slot for an **externally-provided icon**.
+ *
+ * TODO(b/265307726): Rename to `setExternalIcon` or something similar.
+ */
void setIcon(String slot, StatusBarIcon icon);
+
/** */
void setWifiIcon(String slot, WifiIconState state);
@@ -133,9 +151,17 @@
* TAG_PRIMARY to refer to the first icon at a given slot.
*/
void removeIcon(String slot, int tag);
+
/** */
void removeAllIconsForSlot(String slot);
+ /**
+ * Removes all the icons for the given slot.
+ *
+ * Only use this for icons that have come from **an external process**.
+ */
+ void removeAllIconsForExternalSlot(String slot);
+
// TODO: See if we can rename this tunable name.
String ICON_HIDE_LIST = "icon_blacklist";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 9fbe6cb..416bc71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -28,6 +28,8 @@
import android.util.Log;
import android.view.ViewGroup;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -63,6 +65,10 @@
ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
private static final String TAG = "StatusBarIconController";
+ // Use this suffix to prevent external icon slot names from unintentionally overriding our
+ // internal, system-level slot names. See b/255428281.
+ @VisibleForTesting
+ protected static final String EXTERNAL_SLOT_SUFFIX = "__external";
private final StatusBarIconList mStatusBarIconList;
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
@@ -346,21 +352,26 @@
@Override
public void setExternalIcon(String slot) {
- int viewIndex = mStatusBarIconList.getViewIndex(slot, 0);
+ String slotName = createExternalSlotName(slot);
+ int viewIndex = mStatusBarIconList.getViewIndex(slotName, 0);
int height = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_drawing_size);
mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
}
- //TODO: remove this (used in command queue and for 3rd party tiles?)
+ // Override for *both* CommandQueue.Callbacks AND StatusBarIconController.
+ // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
+ // differentiate between those callback methods and StatusBarIconController methods.
+ @Override
public void setIcon(String slot, StatusBarIcon icon) {
+ String slotName = createExternalSlotName(slot);
if (icon == null) {
- removeAllIconsForSlot(slot);
+ removeAllIconsForSlot(slotName);
return;
}
StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon);
- setIcon(slot, holder);
+ setIcon(slotName, holder);
}
private void setIcon(String slot, @NonNull StatusBarIconHolder holder) {
@@ -406,10 +417,12 @@
}
}
- /** */
+ // CommandQueue.Callbacks override
+ // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
+ // differentiate between those callback methods and StatusBarIconController methods.
@Override
public void removeIcon(String slot) {
- removeAllIconsForSlot(slot);
+ removeAllIconsForExternalSlot(slot);
}
/** */
@@ -423,6 +436,11 @@
mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
}
+ @Override
+ public void removeAllIconsForExternalSlot(String slotName) {
+ removeAllIconsForSlot(createExternalSlotName(slotName));
+ }
+
/** */
@Override
public void removeAllIconsForSlot(String slotName) {
@@ -506,4 +524,12 @@
public void onDensityOrFontScaleChanged() {
refreshIconGroups();
}
+
+ private String createExternalSlotName(String slot) {
+ if (slot.endsWith(EXTERNAL_SLOT_SUFFIX)) {
+ return slot;
+ } else {
+ return slot + EXTERNAL_SLOT_SUFFIX;
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 7c1e384..cac4a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -12,11 +12,13 @@
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.WindowManager
+import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.DecorView
import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull
@@ -205,25 +207,74 @@
verify(interactionJankMonitor).end(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN)
}
+ @Test
+ fun testAnimationDoesNotChangeLaunchableViewVisibility_viewVisible() {
+ val touchSurface = createTouchSurface()
+
+ // View is VISIBLE when starting the animation.
+ runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.VISIBLE }
+
+ // View is invisible while the dialog is shown.
+ val dialog = showDialogFromView(touchSurface)
+ assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+
+ // View is visible again when the dialog is dismissed.
+ runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+ assertThat(touchSurface.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testAnimationDoesNotChangeLaunchableViewVisibility_viewInvisible() {
+ val touchSurface = createTouchSurface()
+
+ // View is INVISIBLE when starting the animation.
+ runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.INVISIBLE }
+
+ // View is INVISIBLE while the dialog is shown.
+ val dialog = showDialogFromView(touchSurface)
+ assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+
+ // View is invisible like it was before showing the dialog.
+ runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+ assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun testAnimationDoesNotChangeLaunchableViewVisibility_viewVisibleThenGone() {
+ val touchSurface = createTouchSurface()
+
+ // View is VISIBLE when starting the animation.
+ runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.VISIBLE }
+
+ // View is INVISIBLE while the dialog is shown.
+ val dialog = showDialogFromView(touchSurface)
+ assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+
+ // Some external call makes the View GONE. It remains INVISIBLE while the dialog is shown,
+ // as all visibility changes should be blocked.
+ runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.GONE }
+ assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE)
+
+ // View is restored to GONE once the dialog is dismissed.
+ runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+ assertThat(touchSurface.visibility).isEqualTo(View.GONE)
+ }
+
private fun createAndShowDialog(
animator: DialogLaunchAnimator = dialogLaunchAnimator,
): TestDialog {
val touchSurface = createTouchSurface()
- return runOnMainThreadAndWaitForIdleSync {
- val dialog = TestDialog(context)
- animator.showFromView(dialog, touchSurface)
- dialog
- }
+ return showDialogFromView(touchSurface, animator)
}
private fun createTouchSurface(): View {
return runOnMainThreadAndWaitForIdleSync {
val touchSurfaceRoot = LinearLayout(context)
- val touchSurface = View(context)
+ val touchSurface = TouchSurfaceView(context)
touchSurfaceRoot.addView(touchSurface)
// We need to attach the root to the window manager otherwise the exit animation will
- // be skipped
+ // be skipped.
ViewUtils.attachView(touchSurfaceRoot)
attachedViews.add(touchSurfaceRoot)
@@ -231,6 +282,17 @@
}
}
+ private fun showDialogFromView(
+ touchSurface: View,
+ animator: DialogLaunchAnimator = dialogLaunchAnimator,
+ ): TestDialog {
+ return runOnMainThreadAndWaitForIdleSync {
+ val dialog = TestDialog(context)
+ animator.showFromView(dialog, touchSurface)
+ dialog
+ }
+ }
+
private fun createDialogAndShowFromDialog(animateFrom: Dialog): TestDialog {
return runOnMainThreadAndWaitForIdleSync {
val dialog = TestDialog(context)
@@ -248,6 +310,22 @@
return result
}
+ private class TouchSurfaceView(context: Context) : FrameLayout(context), LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ )
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
+ }
+ }
+
private class TestDialog(context: Context) : Dialog(context) {
companion object {
const val DIALOG_WIDTH = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index 56043e30..34ddf79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -39,7 +39,8 @@
@Test
fun processTouch() {
- overlapDetector.shouldReturn = testCase.isGoodOverlap
+ overlapDetector.shouldReturn =
+ testCase.currentPointers.associate { pointer -> pointer.id to pointer.onSensor }
val actual =
underTest.processTouch(
@@ -56,7 +57,7 @@
data class TestCase(
val event: MotionEvent,
- val isGoodOverlap: Boolean,
+ val currentPointers: List<TestPointer>,
val previousPointerOnSensorId: Int,
val overlayParams: UdfpsOverlayParams,
val expected: TouchProcessorResult,
@@ -91,28 +92,21 @@
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_DOWN,
previousPointerOnSensorId = INVALID_POINTER_ID,
- isGoodOverlap = true,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
expectedInteractionEvent = InteractionEvent.DOWN,
- expectedPointerOnSensorId = POINTER_ID,
- ),
- genPositiveTestCases(
- motionEventAction = MotionEvent.ACTION_DOWN,
- previousPointerOnSensorId = POINTER_ID,
- isGoodOverlap = true,
- expectedInteractionEvent = InteractionEvent.DOWN,
- expectedPointerOnSensorId = POINTER_ID,
+ expectedPointerOnSensorId = POINTER_ID_1,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_DOWN,
previousPointerOnSensorId = INVALID_POINTER_ID,
- isGoodOverlap = false,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
expectedInteractionEvent = InteractionEvent.UNCHANGED,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_DOWN,
- previousPointerOnSensorId = POINTER_ID,
- isGoodOverlap = false,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
expectedInteractionEvent = InteractionEvent.UP,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
@@ -120,109 +114,226 @@
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_MOVE,
previousPointerOnSensorId = INVALID_POINTER_ID,
- isGoodOverlap = true,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
expectedInteractionEvent = InteractionEvent.DOWN,
- expectedPointerOnSensorId = POINTER_ID,
+ expectedPointerOnSensorId = POINTER_ID_1,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_MOVE,
- previousPointerOnSensorId = POINTER_ID,
- isGoodOverlap = true,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
expectedInteractionEvent = InteractionEvent.UNCHANGED,
- expectedPointerOnSensorId = POINTER_ID,
+ expectedPointerOnSensorId = POINTER_ID_1,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_MOVE,
previousPointerOnSensorId = INVALID_POINTER_ID,
- isGoodOverlap = false,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
expectedInteractionEvent = InteractionEvent.UNCHANGED,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_MOVE,
- previousPointerOnSensorId = POINTER_ID,
- isGoodOverlap = false,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
expectedInteractionEvent = InteractionEvent.UP,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_MOVE,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = false),
+ TestPointer(id = POINTER_ID_2, onSensor = true)
+ ),
+ expectedInteractionEvent = InteractionEvent.DOWN,
+ expectedPointerOnSensorId = POINTER_ID_2,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_MOVE,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = false),
+ TestPointer(id = POINTER_ID_2, onSensor = true)
+ ),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = POINTER_ID_2,
+ ),
// MotionEvent.ACTION_UP
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_UP,
previousPointerOnSensorId = INVALID_POINTER_ID,
- isGoodOverlap = true,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
expectedInteractionEvent = InteractionEvent.UP,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_UP,
- previousPointerOnSensorId = POINTER_ID,
- isGoodOverlap = true,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
expectedInteractionEvent = InteractionEvent.UP,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_UP,
previousPointerOnSensorId = INVALID_POINTER_ID,
- isGoodOverlap = false,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
expectedInteractionEvent = InteractionEvent.UNCHANGED,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
- genPositiveTestCases(
- motionEventAction = MotionEvent.ACTION_UP,
- previousPointerOnSensorId = POINTER_ID,
- isGoodOverlap = false,
- expectedInteractionEvent = InteractionEvent.UP,
- expectedPointerOnSensorId = INVALID_POINTER_ID,
- ),
// MotionEvent.ACTION_CANCEL
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_CANCEL,
previousPointerOnSensorId = INVALID_POINTER_ID,
- isGoodOverlap = true,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
expectedInteractionEvent = InteractionEvent.CANCEL,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_CANCEL,
- previousPointerOnSensorId = POINTER_ID,
- isGoodOverlap = true,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
expectedInteractionEvent = InteractionEvent.CANCEL,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_CANCEL,
previousPointerOnSensorId = INVALID_POINTER_ID,
- isGoodOverlap = false,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
expectedInteractionEvent = InteractionEvent.CANCEL,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
genPositiveTestCases(
motionEventAction = MotionEvent.ACTION_CANCEL,
- previousPointerOnSensorId = POINTER_ID,
- isGoodOverlap = false,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
expectedInteractionEvent = InteractionEvent.CANCEL,
expectedPointerOnSensorId = INVALID_POINTER_ID,
),
+ // MotionEvent.ACTION_POINTER_DOWN
+ genPositiveTestCases(
+ motionEventAction =
+ MotionEvent.ACTION_POINTER_DOWN +
+ (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = true),
+ TestPointer(id = POINTER_ID_2, onSensor = false)
+ ),
+ expectedInteractionEvent = InteractionEvent.DOWN,
+ expectedPointerOnSensorId = POINTER_ID_1,
+ ),
+ genPositiveTestCases(
+ motionEventAction =
+ MotionEvent.ACTION_POINTER_DOWN +
+ (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = false),
+ TestPointer(id = POINTER_ID_2, onSensor = true)
+ ),
+ expectedInteractionEvent = InteractionEvent.DOWN,
+ expectedPointerOnSensorId = POINTER_ID_2
+ ),
+ genPositiveTestCases(
+ motionEventAction =
+ MotionEvent.ACTION_POINTER_DOWN +
+ (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = true),
+ TestPointer(id = POINTER_ID_2, onSensor = false)
+ ),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = POINTER_ID_1,
+ ),
+ // MotionEvent.ACTION_POINTER_UP
+ genPositiveTestCases(
+ motionEventAction =
+ MotionEvent.ACTION_POINTER_UP +
+ (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = false),
+ TestPointer(id = POINTER_ID_2, onSensor = false)
+ ),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = INVALID_POINTER_ID
+ ),
+ genPositiveTestCases(
+ motionEventAction =
+ MotionEvent.ACTION_POINTER_UP +
+ (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+ previousPointerOnSensorId = POINTER_ID_2,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = false),
+ TestPointer(id = POINTER_ID_2, onSensor = true)
+ ),
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_POINTER_UP,
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = true),
+ TestPointer(id = POINTER_ID_2, onSensor = false)
+ ),
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID
+ ),
+ genPositiveTestCases(
+ motionEventAction =
+ MotionEvent.ACTION_POINTER_UP +
+ (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+ previousPointerOnSensorId = POINTER_ID_1,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = true),
+ TestPointer(id = POINTER_ID_2, onSensor = false)
+ ),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = POINTER_ID_1
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_POINTER_UP,
+ previousPointerOnSensorId = POINTER_ID_2,
+ currentPointers =
+ listOf(
+ TestPointer(id = POINTER_ID_1, onSensor = false),
+ TestPointer(id = POINTER_ID_2, onSensor = true)
+ ),
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = POINTER_ID_2
+ )
)
.flatten() +
listOf(
- // Unsupported MotionEvent actions.
- genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_DOWN),
- genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_UP),
genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER),
genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE),
- genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT),
+ genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT)
)
.flatten()
}
}
+data class TestPointer(val id: Int, val onSensor: Boolean)
+
/* Display dimensions in native resolution and natural orientation. */
private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400
private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600
/* Placeholder touch parameters. */
-private const val POINTER_ID = 42
+private const val POINTER_ID_1 = 42
+private const val POINTER_ID_2 = 43
private const val NATIVE_MINOR = 2.71828f
private const val NATIVE_MAJOR = 3.14f
private const val ORIENTATION = 1.2345f
@@ -325,7 +436,7 @@
private val MOTION_EVENT =
obtainMotionEvent(
action = 0,
- pointerId = POINTER_ID,
+ pointerId = POINTER_ID_1,
x = 0f,
y = 0f,
minor = 0f,
@@ -338,7 +449,7 @@
/* Template [NormalizedTouchData]. */
private val NORMALIZED_TOUCH_DATA =
NormalizedTouchData(
- POINTER_ID,
+ POINTER_ID_1,
x = 0f,
y = 0f,
NATIVE_MINOR,
@@ -384,7 +495,7 @@
private fun genPositiveTestCases(
motionEventAction: Int,
previousPointerOnSensorId: Int,
- isGoodOverlap: Boolean,
+ currentPointers: List<TestPointer>,
expectedInteractionEvent: InteractionEvent,
expectedPointerOnSensorId: Int
): List<SinglePointerTouchProcessorTest.TestCase> {
@@ -399,22 +510,47 @@
return scaleFactors.flatMap { scaleFactor ->
orientations.map { orientation ->
val overlayParams = orientation.toOverlayParams(scaleFactor)
- val nativeX = orientation.getNativeX(isGoodOverlap)
- val nativeY = orientation.getNativeY(isGoodOverlap)
+
+ val pointerProperties =
+ currentPointers
+ .map { pointer ->
+ val pp = MotionEvent.PointerProperties()
+ pp.id = pointer.id
+ pp
+ }
+ .toList()
+
+ val pointerCoords =
+ currentPointers
+ .map { pointer ->
+ val pc = MotionEvent.PointerCoords()
+ pc.x = orientation.getNativeX(pointer.onSensor) * scaleFactor
+ pc.y = orientation.getNativeY(pointer.onSensor) * scaleFactor
+ pc.touchMinor = NATIVE_MINOR * scaleFactor
+ pc.touchMajor = NATIVE_MAJOR * scaleFactor
+ pc.orientation = orientation.nativeOrientation
+ pc
+ }
+ .toList()
+
val event =
MOTION_EVENT.copy(
action = motionEventAction,
- x = nativeX * scaleFactor,
- y = nativeY * scaleFactor,
- minor = NATIVE_MINOR * scaleFactor,
- major = NATIVE_MAJOR * scaleFactor,
- orientation = orientation.nativeOrientation
+ pointerProperties = pointerProperties,
+ pointerCoords = pointerCoords
)
+
val expectedTouchData =
- NORMALIZED_TOUCH_DATA.copy(
- x = ROTATION_0_INPUTS.getNativeX(isGoodOverlap),
- y = ROTATION_0_INPUTS.getNativeY(isGoodOverlap),
- )
+ if (expectedPointerOnSensorId != INVALID_POINTER_ID) {
+ NORMALIZED_TOUCH_DATA.copy(
+ pointerId = expectedPointerOnSensorId,
+ x = ROTATION_0_INPUTS.getNativeX(isWithinSensor = true),
+ y = ROTATION_0_INPUTS.getNativeY(isWithinSensor = true)
+ )
+ } else {
+ NormalizedTouchData()
+ }
+
val expected =
TouchProcessorResult.ProcessedTouch(
event = expectedInteractionEvent,
@@ -423,7 +559,7 @@
)
SinglePointerTouchProcessorTest.TestCase(
event = event,
- isGoodOverlap = isGoodOverlap,
+ currentPointers = currentPointers,
previousPointerOnSensorId = previousPointerOnSensorId,
overlayParams = overlayParams,
expected = expected,
@@ -436,7 +572,7 @@
motionEventAction: Int
): List<SinglePointerTouchProcessorTest.TestCase> {
val isGoodOverlap = true
- val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID)
+ val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID_1)
return previousPointerOnSensorIds.map { previousPointerOnSensorId ->
val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f)
val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap)
@@ -451,7 +587,7 @@
)
SinglePointerTouchProcessorTest.TestCase(
event = event,
- isGoodOverlap = isGoodOverlap,
+ currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = isGoodOverlap)),
previousPointerOnSensorId = previousPointerOnSensorId,
overlayParams = overlayParams,
expected = TouchProcessorResult.Failure(),
@@ -478,13 +614,23 @@
pc.touchMinor = minor
pc.touchMajor = major
pc.orientation = orientation
+ return obtainMotionEvent(action, arrayOf(pp), arrayOf(pc), time, gestureStart)
+}
+
+private fun obtainMotionEvent(
+ action: Int,
+ pointerProperties: Array<MotionEvent.PointerProperties>,
+ pointerCoords: Array<MotionEvent.PointerCoords>,
+ time: Long,
+ gestureStart: Long,
+): MotionEvent {
return MotionEvent.obtain(
gestureStart /* downTime */,
time /* eventTime */,
action /* action */,
- 1 /* pointerCount */,
- arrayOf(pp) /* pointerProperties */,
- arrayOf(pc) /* pointerCoords */,
+ pointerCoords.size /* pointerCount */,
+ pointerProperties /* pointerProperties */,
+ pointerCoords /* pointerCoords */,
0 /* metaState */,
0 /* buttonState */,
1f /* xPrecision */,
@@ -508,4 +654,19 @@
gestureStart: Long = this.downTime,
) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart)
+private fun MotionEvent.copy(
+ action: Int = this.action,
+ pointerProperties: List<MotionEvent.PointerProperties>,
+ pointerCoords: List<MotionEvent.PointerCoords>,
+ time: Long = this.eventTime,
+ gestureStart: Long = this.downTime
+) =
+ obtainMotionEvent(
+ action,
+ pointerProperties.toTypedArray(),
+ pointerCoords.toTypedArray(),
+ time,
+ gestureStart
+ )
+
private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
new file mode 100644
index 0000000..3e6cc3b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.compose
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.ViewUtils
+import android.widget.FrameLayout
+import androidx.compose.ui.platform.ComposeView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ComposeInitializerTest : SysuiTestCase() {
+ @Test
+ fun testCanAddComposeViewInInitializedWindow() {
+ if (!ComposeFacade.isComposeAvailable()) {
+ return
+ }
+
+ val root = TestWindowRoot(context)
+ try {
+ runOnMainThreadAndWaitForIdleSync { ViewUtils.attachView(root) }
+ assertThat(root.isAttachedToWindow).isTrue()
+
+ runOnMainThreadAndWaitForIdleSync { root.addView(ComposeView(context)) }
+ } finally {
+ runOnMainThreadAndWaitForIdleSync { ViewUtils.detachView(root) }
+ }
+ }
+
+ private fun runOnMainThreadAndWaitForIdleSync(f: () -> Unit) {
+ mContext.mainExecutor.execute(f)
+ waitForIdleSync()
+ }
+
+ class TestWindowRoot(context: Context) : FrameLayout(context) {
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ ComposeFacade.composeInitializer().onAttachedToWindow(this)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ ComposeFacade.composeInitializer().onDetachedFromWindow(this)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index c24c8c7..1687fdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.pipeline
import android.app.Notification
+import android.app.Notification.FLAG_NO_CLEAR
import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
@@ -1451,6 +1452,39 @@
assertThat(mediaDataCaptor.value.semanticActions).isNull()
}
+ @Test
+ fun testNoClearNotOngoing_canDismiss() {
+ mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setOngoing(false)
+ it.setFlag(FLAG_NO_CLEAR, true)
+ }
+ build()
+ }
+ addNotificationAndLoad()
+ assertThat(mediaDataCaptor.value.isClearable).isTrue()
+ }
+
+ @Test
+ fun testOngoing_cannotDismiss() {
+ mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setOngoing(true)
+ }
+ build()
+ }
+ addNotificationAndLoad()
+ assertThat(mediaDataCaptor.value.isClearable).isFalse()
+ }
+
/** Helper function to add a media notification and capture the resulting MediaData */
private fun addNotificationAndLoad() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 19d2d33..1042ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -1,12 +1,16 @@
package com.android.systemui.mediaprojection.appselector
import android.content.ComponentName
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.Test
@@ -21,11 +25,17 @@
private val scope = CoroutineScope(Dispatchers.Unconfined)
private val appSelectorComponentName = ComponentName("com.test", "AppSelector")
+ private val hostUserHandle = UserHandle.of(123)
+ private val otherUserHandle = UserHandle.of(456)
+
private val view: MediaProjectionAppSelectorView = mock()
+ private val featureFlags: FeatureFlags = mock()
private val controller = MediaProjectionAppSelectorController(
taskListProvider,
view,
+ featureFlags,
+ hostUserHandle,
scope,
appSelectorComponentName
)
@@ -98,15 +108,72 @@
)
}
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
+ givenEnterprisePoliciesFeatureFlag(enabled = false)
+
+ val tasks = listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ verify(view).bind(
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ )
+ }
+
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
+ givenEnterprisePoliciesFeatureFlag(enabled = true)
+
+ val tasks = listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ // TODO(b/233348916) should filter depending on the policies
+ verify(view).bind(
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ )
+ }
+
+ private fun givenEnterprisePoliciesFeatureFlag(enabled: Boolean) {
+ whenever(featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+ .thenReturn(enabled)
+ }
+
private fun createRecentTask(
taskId: Int,
- topActivityComponent: ComponentName? = null
+ topActivityComponent: ComponentName? = null,
+ userId: Int = hostUserHandle.identifier
): RecentTask {
return RecentTask(
taskId = taskId,
topActivityComponent = topActivityComponent,
baseIntentComponent = ComponentName("com", "Test"),
- userId = 0,
+ userId = userId,
colorBackground = 0
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
index 4d89495..3b85dba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -39,11 +39,10 @@
@Test
public void needReinflate_differentLength() {
- // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
- // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
PendingIntent pendingIntent =
- PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
Notification.Action action =
createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
assertThat(NotificationUiAdjustment.needReinflate(
@@ -54,11 +53,10 @@
@Test
public void needReinflate_differentLabels() {
- // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
- // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
PendingIntent pendingIntent =
- PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
Notification.Action firstAction =
createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
Notification.Action secondAction =
@@ -72,11 +70,10 @@
@Test
public void needReinflate_differentIcons() {
- // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
- // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
PendingIntent pendingIntent =
- PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
Notification.Action firstAction =
createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent).build();
Notification.Action secondAction =
@@ -91,14 +88,15 @@
@Test
public void needReinflate_differentPendingIntent() {
- // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
- // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
PendingIntent firstPendingIntent =
- PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_VIEW),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent.getActivity(mContext, 0,
+ new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
PendingIntent secondPendingIntent =
- PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_PROCESS_TEXT),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent.getActivity(mContext, 0,
+ new Intent(Intent.ACTION_PROCESS_TEXT)
+ .setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
Notification.Action firstAction =
createActionBuilder("same", R.drawable.ic_corp_icon, firstPendingIntent)
.build();
@@ -114,11 +112,10 @@
@Test
public void needReinflate_differentChoices() {
- // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
- // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
PendingIntent pendingIntent =
- PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
RemoteInput firstRemoteInput =
createRemoteInput("same", "same", new CharSequence[] {"first"});
@@ -142,11 +139,10 @@
@Test
public void needReinflate_differentRemoteInputLabel() {
- // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
- // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
PendingIntent pendingIntent =
- PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
RemoteInput firstRemoteInput =
createRemoteInput("same", "first", new CharSequence[] {"same"});
@@ -170,11 +166,10 @@
@Test
public void needReinflate_negative() {
- // TODO(b/174258598) Please replace FLAG_MUTABLE_UNAUDITED below
- // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
PendingIntent pendingIntent =
- PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
RemoteInput firstRemoteInput =
createRemoteInput("same", "same", new CharSequence[] {"same"});
RemoteInput secondRemoteInput =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 601771d..58fe2a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -885,7 +885,8 @@
private NotificationEntry createBubble(String groupKey, Integer groupAlert) {
Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder(
- PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
PendingIntent.FLAG_MUTABLE),
Icon.createWithResource(mContext.getResources(), R.drawable.android))
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
new file mode 100644
index 0000000..3e90ed9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.pm.PackageManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_FLIPPED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.tuner.TunerService
+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 org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class KeyguardBypassControllerTest : SysuiTestCase() {
+
+ private lateinit var keyguardBypassController: KeyguardBypassController
+ private lateinit var postureControllerCallback: DevicePostureController.Callback
+ @Mock private lateinit var tunerService: TunerService
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock private lateinit var devicePostureController: DevicePostureController
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var packageManager: PackageManager
+ @Captor
+ private val postureCallbackCaptor =
+ ArgumentCaptor.forClass(DevicePostureController.Callback::class.java)
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+ @Before
+ fun setUp() {
+ context.setMockPackageManager(packageManager)
+ whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true)
+ whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+ }
+
+ @After
+ fun tearDown() {
+ reset(devicePostureController)
+ reset(keyguardStateController)
+ }
+
+ private fun defaultConfigPostureClosed() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_auth_supported_posture,
+ DEVICE_POSTURE_CLOSED
+ )
+ initKeyguardBypassController()
+ verify(devicePostureController).addCallback(postureCallbackCaptor.capture())
+ postureControllerCallback = postureCallbackCaptor.value
+ }
+
+ private fun defaultConfigPostureOpened() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_auth_supported_posture,
+ DEVICE_POSTURE_OPENED
+ )
+ initKeyguardBypassController()
+ verify(devicePostureController).addCallback(postureCallbackCaptor.capture())
+ postureControllerCallback = postureCallbackCaptor.value
+ }
+
+ private fun defaultConfigPostureFlipped() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_auth_supported_posture,
+ DEVICE_POSTURE_FLIPPED
+ )
+ initKeyguardBypassController()
+ verify(devicePostureController).addCallback(postureCallbackCaptor.capture())
+ postureControllerCallback = postureCallbackCaptor.value
+ }
+
+ private fun defaultConfigPostureUnknown() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_auth_supported_posture,
+ DEVICE_POSTURE_UNKNOWN
+ )
+ initKeyguardBypassController()
+ verify(devicePostureController, never()).addCallback(postureCallbackCaptor.capture())
+ }
+
+ private fun initKeyguardBypassController() {
+ keyguardBypassController =
+ KeyguardBypassController(
+ context,
+ tunerService,
+ statusBarStateController,
+ lockscreenUserManager,
+ keyguardStateController,
+ shadeExpansionStateManager,
+ devicePostureController,
+ dumpManager
+ )
+ }
+
+ @Test
+ fun configDevicePostureClosed_matchState_isPostureAllowedForFaceAuth_returnTrue() {
+ defaultConfigPostureClosed()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue()
+ }
+
+ @Test
+ fun configDevicePostureOpen_matchState_isPostureAllowedForFaceAuth_returnTrue() {
+ defaultConfigPostureOpened()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue()
+ }
+
+ @Test
+ fun configDevicePostureFlipped_matchState_isPostureAllowedForFaceAuth_returnTrue() {
+ defaultConfigPostureFlipped()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue()
+ }
+
+ @Test
+ fun configDevicePostureClosed_changeOpened_isPostureAllowedForFaceAuth_returnFalse() {
+ defaultConfigPostureClosed()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+ }
+
+ @Test
+ fun configDevicePostureClosed_changeFlipped_isPostureAllowedForFaceAuth_returnFalse() {
+ defaultConfigPostureClosed()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+ }
+
+ @Test
+ fun configDevicePostureOpened_changeClosed_isPostureAllowedForFaceAuth_returnFalse() {
+ defaultConfigPostureOpened()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+ }
+
+ @Test
+ fun configDevicePostureOpened_changeFlipped_isPostureAllowedForFaceAuth_returnFalse() {
+ defaultConfigPostureOpened()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+ }
+
+ @Test
+ fun configDevicePostureFlipped_changeClosed_isPostureAllowedForFaceAuth_returnFalse() {
+ defaultConfigPostureFlipped()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+ }
+
+ @Test
+ fun configDevicePostureFlipped_changeOpened_isPostureAllowedForFaceAuth_returnFalse() {
+ defaultConfigPostureFlipped()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse()
+ }
+
+ @Test
+ fun defaultConfigPostureClosed_canOverrideByPassAlways_shouldReturnFalse() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 1 /* FACE_UNLOCK_BYPASS_ALWAYS */
+ )
+
+ defaultConfigPostureClosed()
+
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ assertThat(keyguardBypassController.bypassEnabled).isFalse()
+ }
+
+ @Test
+ fun defaultConfigPostureUnknown_canNotOverrideByPassAlways_shouldReturnTrue() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 1 /* FACE_UNLOCK_BYPASS_ALWAYS */
+ )
+
+ defaultConfigPostureUnknown()
+
+ assertThat(keyguardBypassController.bypassEnabled).isTrue()
+ }
+
+ @Test
+ fun defaultConfigPostureUnknown_canNotOverrideByPassNever_shouldReturnFalse() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 2 /* FACE_UNLOCK_BYPASS_NEVER */
+ )
+
+ defaultConfigPostureUnknown()
+
+ assertThat(keyguardBypassController.bypassEnabled).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
new file mode 100644
index 0000000..3bc288a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY
+import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class StatusBarIconControllerImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: StatusBarIconControllerImpl
+
+ private lateinit var iconList: StatusBarIconList
+ private val iconGroup: StatusBarIconController.IconManager = mock()
+
+ @Before
+ fun setUp() {
+ iconList = StatusBarIconList(arrayOf())
+ underTest =
+ StatusBarIconControllerImpl(
+ context,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ iconList,
+ mock(),
+ )
+ underTest.addIconGroup(iconGroup)
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_bothDisplayed() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ val externalIcon =
+ StatusBarIcon(
+ "external.package",
+ UserHandle.ALL,
+ /* iconId= */ 2,
+ /* iconLevel= */ 0,
+ /* number= */ 0,
+ "contentDescription",
+ )
+ underTest.setIcon(slotName, externalIcon)
+
+ assertThat(iconList.slots).hasSize(2)
+ // Whichever was added last comes first
+ assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(iconList.slots[1].name).isEqualTo(slotName)
+ assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
+ assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveIcon_internalStays() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ underTest.setIcon(slotName, createExternalIcon())
+
+ // WHEN the external icon is removed via #removeIcon
+ underTest.removeIcon(slotName)
+
+ // THEN the external icon is removed but the internal icon remains
+ // Note: [StatusBarIconList] never removes slots from its list, it just sets the holder for
+ // the slot to null when an icon is removed.
+ assertThat(iconList.slots).hasSize(2)
+ assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(iconList.slots[1].name).isEqualTo(slotName)
+ assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
+ assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+
+ verify(iconGroup).onRemoveIcon(0)
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveAll_internalStays() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ underTest.setIcon(slotName, createExternalIcon())
+
+ // WHEN the external icon is removed via #removeAllIconsForExternalSlot
+ underTest.removeAllIconsForExternalSlot(slotName)
+
+ // THEN the external icon is removed but the internal icon remains
+ assertThat(iconList.slots).hasSize(2)
+ assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(iconList.slots[1].name).isEqualTo(slotName)
+ assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
+ assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+
+ verify(iconGroup).onRemoveIcon(0)
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_externalRemoved_viaSetNull_internalStays() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ underTest.setIcon(slotName, createExternalIcon())
+
+ // WHEN the external icon is removed via a #setIcon(null)
+ underTest.setIcon(slotName, /* icon= */ null)
+
+ // THEN the external icon is removed but the internal icon remains
+ assertThat(iconList.slots).hasSize(2)
+ assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(iconList.slots[1].name).isEqualTo(slotName)
+ assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal
+ assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+
+ verify(iconGroup).onRemoveIcon(0)
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_internalRemoved_viaRemove_externalStays() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ underTest.setIcon(slotName, createExternalIcon())
+
+ // WHEN the internal icon is removed via #removeIcon
+ underTest.removeIcon(slotName, /* tag= */ 0)
+
+ // THEN the external icon is removed but the internal icon remains
+ assertThat(iconList.slots).hasSize(2)
+ assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(iconList.slots[1].name).isEqualTo(slotName)
+ assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
+ assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
+
+ verify(iconGroup).onRemoveIcon(1)
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_internalRemoved_viaRemoveAll_externalStays() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ underTest.setIcon(slotName, createExternalIcon())
+
+ // WHEN the internal icon is removed via #removeAllIconsForSlot
+ underTest.removeAllIconsForSlot(slotName)
+
+ // THEN the external icon is removed but the internal icon remains
+ assertThat(iconList.slots).hasSize(2)
+ assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(iconList.slots[1].name).isEqualTo(slotName)
+ assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
+ assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
+
+ verify(iconGroup).onRemoveIcon(1)
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_internalUpdatedIndependently() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ val startingExternalIcon =
+ StatusBarIcon(
+ "external.package",
+ UserHandle.ALL,
+ /* iconId= */ 20,
+ /* iconLevel= */ 0,
+ /* number= */ 0,
+ "externalDescription",
+ )
+ underTest.setIcon(slotName, startingExternalIcon)
+
+ // WHEN the internal icon is updated
+ underTest.setIcon(slotName, /* resourceId= */ 11, "newContentDescription")
+
+ // THEN only the internal slot gets the updates
+ val internalSlot = iconList.slots[1]
+ val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!!
+ assertThat(internalSlot.name).isEqualTo(slotName)
+ assertThat(internalHolder.icon!!.contentDescription).isEqualTo("newContentDescription")
+ assertThat(internalHolder.icon!!.icon.resId).isEqualTo(11)
+
+ // And the external slot has its own values
+ val externalSlot = iconList.slots[0]
+ val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!!
+ assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(externalHolder.icon!!.contentDescription).isEqualTo("externalDescription")
+ assertThat(externalHolder.icon!!.icon.resId).isEqualTo(20)
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_externalUpdatedIndependently() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ val startingExternalIcon =
+ StatusBarIcon(
+ "external.package",
+ UserHandle.ALL,
+ /* iconId= */ 20,
+ /* iconLevel= */ 0,
+ /* number= */ 0,
+ "externalDescription",
+ )
+ underTest.setIcon(slotName, startingExternalIcon)
+
+ // WHEN the external icon is updated
+ val newExternalIcon =
+ StatusBarIcon(
+ "external.package",
+ UserHandle.ALL,
+ /* iconId= */ 21,
+ /* iconLevel= */ 0,
+ /* number= */ 0,
+ "newExternalDescription",
+ )
+ underTest.setIcon(slotName, newExternalIcon)
+
+ // THEN only the external slot gets the updates
+ val externalSlot = iconList.slots[0]
+ val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!!
+ assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(externalHolder.icon!!.contentDescription).isEqualTo("newExternalDescription")
+ assertThat(externalHolder.icon!!.icon.resId).isEqualTo(21)
+
+ // And the internal slot has its own values
+ val internalSlot = iconList.slots[1]
+ val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!!
+ assertThat(internalSlot.name).isEqualTo(slotName)
+ assertThat(internalHolder.icon!!.contentDescription).isEqualTo("contentDescription")
+ assertThat(internalHolder.icon!!.icon.resId).isEqualTo(10)
+ }
+
+ @Test
+ fun externalSlot_alreadyEndsWithSuffix_suffixNotAddedTwice() {
+ underTest.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
+
+ assertThat(iconList.slots).hasSize(1)
+ assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX")
+ }
+
+ private fun createExternalIcon(): StatusBarIcon {
+ return StatusBarIcon(
+ "external.package",
+ UserHandle.ALL,
+ /* iconId= */ 2,
+ /* iconLevel= */ 0,
+ /* number= */ 0,
+ "contentDescription",
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 9764b8c..4aa86c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -197,7 +197,7 @@
public void testPinEntry_logsPeek() {
// Needs full screen intent in order to be pinned
final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
- new Intent(), PendingIntent.FLAG_MUTABLE);
+ new Intent().setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE);
HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
entryToPin.setEntry(new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index 5e2fa98..85052e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
@@ -574,7 +574,8 @@
private void setupAppGeneratedReplies(
CharSequence[] smartReplies, boolean allowSystemGeneratedReplies) {
PendingIntent pendingIntent =
- PendingIntent.getBroadcast(mContext, 0, TEST_INTENT,
+ PendingIntent.getBroadcast(mContext, 0,
+ TEST_INTENT.setPackage(mContext.getPackageName()),
PendingIntent.FLAG_MUTABLE);
Notification.Action action =
new Notification.Action.Builder(null, "Test Action", pendingIntent).build();
@@ -606,7 +607,8 @@
}
private Notification.Action.Builder createActionBuilder(String actionTitle, Intent intent) {
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+ intent.setPackage(mContext.getPackageName()),
PendingIntent.FLAG_MUTABLE);
return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 0cca7b2..391c8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -140,7 +140,8 @@
private void setTestPendingIntent(RemoteInputViewController controller) {
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent(TEST_ACTION), PendingIntent.FLAG_MUTABLE);
+ new Intent(TEST_ACTION).setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();
RemoteInput[] inputs = {input};
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index a7ff59c..d9d8b63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -488,7 +488,8 @@
private SmartReplyView.SmartReplies createSmartReplies(CharSequence[] choices,
boolean fromAssistant) {
PendingIntent pendingIntent =
- PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION),
+ PendingIntent.getBroadcast(mContext, 0,
+ new Intent(TEST_ACTION).setPackage(mContext.getPackageName()),
PendingIntent.FLAG_MUTABLE);
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
return new SmartReplyView.SmartReplies(
@@ -505,7 +506,8 @@
private Notification.Action createAction(String actionTitle) {
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent(TEST_ACTION), PendingIntent.FLAG_MUTABLE);
+ new Intent(TEST_ACTION).setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent).build();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
index 8176dd0..1bdee36 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
@@ -19,9 +19,10 @@
import android.graphics.Rect
class FakeOverlapDetector : OverlapDetector {
- var shouldReturn: Boolean = false
+ var shouldReturn: Map<Int, Boolean> = mapOf()
override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
- return shouldReturn
+ return shouldReturn[touchData.pointerId]
+ ?: error("Unexpected PointerId not declared in TestCase currentPointers")
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2d6d29a..926c6c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -98,6 +98,10 @@
}
@Override
+ public void removeAllIconsForExternalSlot(String slot) {
+ }
+
+ @Override
public void setIconAccessibilityLiveRegion(String slot, int mode) {
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index 5a868a4..cfb959e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -22,8 +22,8 @@
import android.os.Handler
import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
@@ -58,7 +58,7 @@
@BindsInstance sensorManager: SensorManager,
@BindsInstance @UnfoldMain handler: Handler,
@BindsInstance @UnfoldMain executor: Executor,
- @BindsInstance @UnfoldBackground backgroundExecutor: Executor,
+ @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
@BindsInstance windowManager: IWindowManager,
@BindsInstance contentResolver: ContentResolver = context.contentResolver
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 3fa5469..31616fa 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -16,9 +16,7 @@
package com.android.systemui.unfold
-import android.hardware.SensorManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldBackground
import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
@@ -34,55 +32,18 @@
import dagger.Module
import dagger.Provides
import java.util.Optional
-import java.util.concurrent.Executor
+import javax.inject.Provider
import javax.inject.Singleton
-@Module
+@Module(includes = [UnfoldSharedInternalModule::class])
class UnfoldSharedModule {
@Provides
@Singleton
- fun unfoldTransitionProgressProvider(
- config: UnfoldTransitionConfig,
- scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
- tracingListener: ATraceLoggerTransitionProgressListener,
- foldStateProvider: FoldStateProvider
- ): Optional<UnfoldTransitionProgressProvider> =
- if (!config.isEnabled) {
- Optional.empty()
- } else {
- val baseProgressProvider =
- if (config.isHingeAngleEnabled) {
- PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
- } else {
- FixedTimingTransitionProgressProvider(foldStateProvider)
- }
- Optional.of(
- scaleAwareProviderFactory.wrap(baseProgressProvider).apply {
- // Always present callback that logs animation beginning and end.
- addCallback(tracingListener)
- }
- )
- }
-
- @Provides
- @Singleton
fun provideFoldStateProvider(
deviceFoldStateProvider: DeviceFoldStateProvider
): FoldStateProvider = deviceFoldStateProvider
@Provides
- fun hingeAngleProvider(
- config: UnfoldTransitionConfig,
- sensorManager: SensorManager,
- @UnfoldBackground executor: Executor
- ): HingeAngleProvider =
- if (config.isHingeAngleEnabled) {
- HingeSensorAngleProvider(sensorManager, executor)
- } else {
- EmptyHingeAngleProvider
- }
-
- @Provides
@Singleton
fun unfoldKeyguardVisibilityProvider(
impl: UnfoldKeyguardVisibilityManagerImpl
@@ -94,3 +55,51 @@
impl: UnfoldKeyguardVisibilityManagerImpl
): UnfoldKeyguardVisibilityManager = impl
}
+
+/**
+ * Needed as methods inside must be public, but their parameters can be internal (and, a public
+ * method can't have internal parameters). Making the module internal and included in a public one
+ * fixes the issue.
+ */
+@Module
+internal class UnfoldSharedInternalModule {
+ @Provides
+ @Singleton
+ fun unfoldTransitionProgressProvider(
+ config: UnfoldTransitionConfig,
+ scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+ tracingListener: ATraceLoggerTransitionProgressListener,
+ physicsBasedUnfoldTransitionProgressProvider:
+ Provider<PhysicsBasedUnfoldTransitionProgressProvider>,
+ fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+ ): Optional<UnfoldTransitionProgressProvider> {
+ if (!config.isEnabled) {
+ return Optional.empty()
+ }
+ val baseProgressProvider =
+ if (config.isHingeAngleEnabled) {
+ physicsBasedUnfoldTransitionProgressProvider.get()
+ } else {
+ fixedTimingTransitionProgressProvider.get()
+ }
+
+ return Optional.of(
+ scaleAwareProviderFactory.wrap(baseProgressProvider).apply {
+ // Always present callback that logs animation beginning and end.
+ addCallback(tracingListener)
+ }
+ )
+ }
+
+ @Provides
+ fun hingeAngleProvider(
+ config: UnfoldTransitionConfig,
+ hingeAngleSensorProvider: Provider<HingeSensorAngleProvider>
+ ): HingeAngleProvider {
+ return if (config.isHingeAngleEnabled) {
+ hingeAngleSensorProvider.get()
+ } else {
+ EmptyHingeAngleProvider
+ }
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index a1ed178..aa93c629 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -37,29 +37,29 @@
* This should **never** be called from sysui, as the object is already provided in that process.
*/
fun createUnfoldSharedComponent(
- context: Context,
- config: UnfoldTransitionConfig,
- screenStatusProvider: ScreenStatusProvider,
- foldProvider: FoldProvider,
- activityTypeProvider: CurrentActivityTypeProvider,
- sensorManager: SensorManager,
- mainHandler: Handler,
- mainExecutor: Executor,
- backgroundExecutor: Executor,
- tracingTagPrefix: String,
- windowManager: IWindowManager,
+ context: Context,
+ config: UnfoldTransitionConfig,
+ screenStatusProvider: ScreenStatusProvider,
+ foldProvider: FoldProvider,
+ activityTypeProvider: CurrentActivityTypeProvider,
+ sensorManager: SensorManager,
+ mainHandler: Handler,
+ mainExecutor: Executor,
+ singleThreadBgExecutor: Executor,
+ tracingTagPrefix: String,
+ windowManager: IWindowManager,
): UnfoldSharedComponent =
- DaggerUnfoldSharedComponent.factory()
- .create(
- context,
- config,
- screenStatusProvider,
- foldProvider,
- activityTypeProvider,
- sensorManager,
- mainHandler,
- mainExecutor,
- backgroundExecutor,
- tracingTagPrefix,
- windowManager,
- )
+ DaggerUnfoldSharedComponent.factory()
+ .create(
+ context,
+ config,
+ screenStatusProvider,
+ foldProvider,
+ activityTypeProvider,
+ sensorManager,
+ mainHandler,
+ mainExecutor,
+ singleThreadBgExecutor,
+ tracingTagPrefix,
+ windowManager,
+ )
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
similarity index 89%
rename from packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
index 6074795..dcac531 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldSingleThreadBg.kt
@@ -18,8 +18,7 @@
/**
* Alternative to [UiBackground] qualifier annotation in unfold module.
+ *
* It is needed as we can't depend on SystemUI code in this module.
*/
-@Qualifier
-@Retention(AnnotationRetention.RUNTIME)
-annotation class UnfoldBackground
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldSingleThreadBg
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index fa59cb4..4622464 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -24,11 +24,13 @@
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import javax.inject.Inject
/** Emits animation progress with fixed timing after unfolding */
-internal class FixedTimingTransitionProgressProvider(
- private val foldStateProvider: FoldStateProvider
-) : UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener {
+internal class FixedTimingTransitionProgressProvider
+@Inject
+constructor(private val foldStateProvider: FoldStateProvider) :
+ UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener {
private val animatorListener = AnimatorListener()
private val animator =
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 074b1e1..6ffbe5a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -33,9 +33,10 @@
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
import com.android.systemui.unfold.updates.name
+import javax.inject.Inject
/** Maps fold updates to unfold transition progress using DynamicAnimation. */
-class PhysicsBasedUnfoldTransitionProgressProvider(
+class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
private val foldStateProvider: FoldStateProvider
) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 5b45897..97c9ba9 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -79,6 +79,7 @@
screenStatusProvider.addCallback(screenListener)
hingeAngleProvider.addCallback(hingeAngleListener)
rotationChangeProvider.addCallback(rotationListener)
+ activityTypeProvider.init()
}
override fun stop() {
@@ -87,6 +88,7 @@
hingeAngleProvider.removeCallback(hingeAngleListener)
hingeAngleProvider.stop()
rotationChangeProvider.removeCallback(rotationListener)
+ activityTypeProvider.uninit()
}
override fun addCallback(listener: FoldUpdatesListener) {
@@ -115,19 +117,17 @@
}
val isClosing = angle < lastHingeAngle
- val closingThreshold = getClosingThreshold()
- val closingThresholdMet = closingThreshold == null || angle < closingThreshold
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
val screenAvailableEventSent = isUnfoldHandled
if (isClosing // hinge angle should be decreasing since last update
- && closingThresholdMet // hinge angle is below certain threshold
&& !closingEventDispatched // we haven't sent closing event already
&& !isFullyOpened // do not send closing event if we are in fully opened hinge
// angle range as closing threshold could overlap this range
&& screenAvailableEventSent // do not send closing event if we are still in
// the process of turning on the inner display
+ && isClosingThresholdMet(angle) // hinge angle is below certain threshold.
) {
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
}
@@ -146,6 +146,11 @@
outputListeners.forEach { it.onHingeAngleUpdate(angle) }
}
+ private fun isClosingThresholdMet(currentAngle: Float) : Boolean {
+ val closingThreshold = getClosingThreshold()
+ return closingThreshold == null || currentAngle < closingThreshold
+ }
+
/**
* Fold animation should be started only after the threshold returned here.
*
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index 577137c..89fb12e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -20,35 +20,43 @@
import android.hardware.SensorManager
import android.os.Trace
import androidx.core.util.Consumer
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import java.util.concurrent.Executor
+import javax.inject.Inject
-internal class HingeSensorAngleProvider(
+internal class HingeSensorAngleProvider
+@Inject
+constructor(
private val sensorManager: SensorManager,
- private val executor: Executor
-) :
- HingeAngleProvider {
+ @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor
+) : HingeAngleProvider {
private val sensorListener = HingeAngleSensorListener()
private val listeners: MutableList<Consumer<Float>> = arrayListOf()
var started = false
- override fun start() = executor.execute {
- if (started) return@execute
- Trace.beginSection("HingeSensorAngleProvider#start")
- val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
- sensorManager.registerListener(
- sensorListener,
- sensor,
- SensorManager.SENSOR_DELAY_FASTEST
- )
- Trace.endSection()
- started = true
+ override fun start() {
+ singleThreadBgExecutor.execute {
+ if (started) return@execute
+ Trace.beginSection("HingeSensorAngleProvider#start")
+ val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
+ sensorManager.registerListener(
+ sensorListener,
+ sensor,
+ SensorManager.SENSOR_DELAY_FASTEST
+ )
+ Trace.endSection()
+
+ started = true
+ }
}
- override fun stop() = executor.execute {
- if (!started) return@execute
- sensorManager.unregisterListener(sensorListener)
- started = false
+ override fun stop() {
+ singleThreadBgExecutor.execute {
+ if (!started) return@execute
+ sensorManager.unregisterListener(sensorListener)
+ started = false
+ }
}
override fun removeCallback(listener: Consumer<Float>) {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
index d0e6cdc..34e7c38 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
@@ -16,6 +16,11 @@
interface CurrentActivityTypeProvider {
val isHomeActivity: Boolean?
+
+ /** Starts listening for task updates. */
+ fun init() {}
+ /** Stop listening for task updates. */
+ fun uninit() {}
}
class EmptyCurrentActivityTypeProvider(override val isHomeActivity: Boolean? = null) :
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 81fbd78..939047f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2651,8 +2651,8 @@
final CharSequence serviceLabel;
final Drawable serviceIcon;
synchronized (mLock) {
- serviceLabel = mService.getServiceLabelLocked();
- serviceIcon = mService.getServiceIconLocked();
+ serviceIcon = getServiceIcon(response);
+ serviceLabel = getServiceLabel(response);
}
if (serviceLabel == null || serviceIcon == null) {
wtf(null, "showSaveLocked(): no service label or icon");
@@ -2662,7 +2662,8 @@
getUiForShowing().showSaveUi(serviceLabel, serviceIcon,
mService.getServicePackageName(), saveInfo, this,
- mComponentName, this, mPendingSaveUi, isUpdate, mCompatMode);
+ mComponentName, this, mPendingSaveUi, isUpdate, mCompatMode,
+ response.getShowSaveDialogIcon());
if (client != null) {
try {
client.setSaveUiState(id, true);
@@ -3600,9 +3601,13 @@
if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed.");
return false;
}
+
}
- final Drawable serviceIcon = getServiceIcon();
+ Drawable serviceIcon = null;
+ synchronized (mLock) {
+ serviceIcon = getServiceIcon(response);
+ }
getUiForShowing().showFillDialog(filledId, response, filterText,
mService.getServicePackageName(), mComponentName, serviceIcon, this,
@@ -3610,13 +3615,64 @@
return true;
}
+ /**
+ * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able
+ * to be fetched, use the default provider icon instead
+ *
+ * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise
+ */
@SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
// actually the same object as mLock.
// TODO: Expose mService.mLock or redesign instead.
- private Drawable getServiceIcon() {
- synchronized (mLock) {
- return mService.getServiceIconLocked();
+ @GuardedBy("mLock")
+ private Drawable getServiceIcon(FillResponse response) {
+ Drawable serviceIcon = null;
+ // Try to get the custom Icon, if one was passed through FillResponse
+ int iconResourceId = response.getIconResourceId();
+ if (iconResourceId != 0) {
+ serviceIcon = mService.getMaster().getContext().getPackageManager()
+ .getDrawable(
+ mService.getServicePackageName(),
+ iconResourceId,
+ null);
}
+
+ // Custom icon wasn't fetched, use the default package icon instead
+ if (serviceIcon == null) {
+ serviceIcon = mService.getServiceIconLocked();
+ }
+
+ return serviceIcon;
+ }
+
+ /**
+ * Get the custom label that was passed through FillResponse. If the custom label
+ * wasn't able to be fetched, use the default provider icon instead
+ *
+ * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise
+ */
+ @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
+ // actually the same object as mLock.
+ // TODO: Expose mService.mLock or redesign instead.
+ @GuardedBy("mLock")
+ private CharSequence getServiceLabel(FillResponse response) {
+ CharSequence serviceLabel = null;
+ // Try to get the custom Service name, if one was passed through FillResponse
+ int customServiceNameId = response.getServiceDisplayNameResourceId();
+ if (customServiceNameId != 0) {
+ serviceLabel = mService.getMaster().getContext().getPackageManager()
+ .getText(
+ mService.getServicePackageName(),
+ customServiceNameId,
+ null);
+ }
+
+ // Custom label wasn't fetched, use the default package name instead
+ if (serviceLabel == null) {
+ serviceLabel = mService.getServiceLabelLocked();
+ }
+
+ return serviceLabel;
}
/**
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 5f0f9a3..7db6e6f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -315,7 +315,7 @@
@Nullable String servicePackageName, @NonNull SaveInfo info,
@NonNull ValueFinder valueFinder, @NonNull ComponentName componentName,
@NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi,
- boolean isUpdate, boolean compatMode) {
+ boolean isUpdate, boolean compatMode, boolean showServiceIcon) {
if (sVerbose) {
Slog.v(TAG, "showSaveUi(update=" + isUpdate + ") for " + componentName.toShortString()
+ ": " + info);
@@ -379,7 +379,7 @@
public void startIntentSender(IntentSender intentSender, Intent intent) {
callback.startIntentSender(intentSender, intent);
}
- }, mUiModeMgr.isNightMode(), isUpdate, compatMode);
+ }, mUiModeMgr.isNightMode(), isUpdate, compatMode, showServiceIcon);
});
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index c2c630e..72a38eb 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -117,7 +117,9 @@
final LayoutInflater inflater = LayoutInflater.from(mContext);
final View decor = inflater.inflate(R.layout.autofill_fill_dialog, null);
- setServiceIcon(decor, serviceIcon);
+ if (response.getShowFillDialogIcon()) {
+ setServiceIcon(decor, serviceIcon);
+ }
setHeader(decor, response);
mVisibleDatasetsMaxCount = getVisibleDatasetsMaxCount();
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 8c2c964..7db27ac 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -177,7 +177,7 @@
@Nullable String servicePackageName, @NonNull ComponentName componentName,
@NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
@NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener,
- boolean nightMode, boolean isUpdate, boolean compatMode) {
+ boolean nightMode, boolean isUpdate, boolean compatMode, boolean showServiceIcon) {
if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
mPendingUi = pendingUi;
@@ -288,7 +288,9 @@
}
titleView.setText(mTitle);
- setServiceIcon(context, view, serviceIcon);
+ if (showServiceIcon) {
+ setServiceIcon(context, view, serviceIcon);
+ }
final boolean hasCustomDescription =
applyCustomDescription(context, view, valueFinder, info);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index dc475f6..8c09dcd 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -25,6 +25,7 @@
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
@@ -722,17 +723,6 @@
}
@Override
- public void setFrameworkSchedulingEnabledForUser(int userId, boolean isEnabled) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId,
- "setFrameworkSchedulingEnabledForUser()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.setFrameworkSchedulingEnabled(isEnabled);
- }
- }
-
- @Override
public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
throws RemoteException {
if (isUserReadyForBackup(userId)) {
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index fe0e1c6..0bb25e3 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -45,12 +45,9 @@
private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>();
public static void schedule(int userId, Context ctx, long minDelay,
- UserBackupManagerService userBackupManagerService) {
- if (!userBackupManagerService.isFrameworkSchedulingEnabled()) return;
-
+ BackupManagerConstants constants) {
JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService);
- final BackupManagerConstants constants = userBackupManagerService.getConstants();
synchronized (constants) {
builder.setRequiresDeviceIdle(true)
.setRequiredNetworkType(constants.getFullBackupRequiredNetworkType())
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index 164bbea..058dcae 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -64,16 +64,14 @@
@VisibleForTesting
public static final int MAX_JOB_ID = 52418896;
- public static void schedule(int userId, Context ctx,
- UserBackupManagerService userBackupManagerService) {
- schedule(userId, ctx, 0, userBackupManagerService);
+ public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
+ schedule(userId, ctx, 0, constants);
}
public static void schedule(int userId, Context ctx, long delay,
- UserBackupManagerService userBackupManagerService) {
+ BackupManagerConstants constants) {
synchronized (KeyValueBackupJob.class) {
- if (sScheduledForUserId.get(userId)
- || !userBackupManagerService.isFrameworkSchedulingEnabled()) {
+ if (sScheduledForUserId.get(userId)) {
return;
}
@@ -82,7 +80,6 @@
final int networkType;
final boolean needsCharging;
- final BackupManagerConstants constants = userBackupManagerService.getConstants();
synchronized (constants) {
interval = constants.getKeyValueBackupIntervalMilliseconds();
fuzz = constants.getKeyValueBackupFuzzMilliseconds();
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 40a4af9..6ba01d7 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1958,8 +1958,8 @@
}
// We don't want the backup jobs to kick in any time soon.
// Reschedules them to run in the distant future.
- KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, this);
- FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, this);
+ KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants);
+ FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants);
} finally {
Binder.restoreCallingIdentity(oldToken);
}
@@ -2088,7 +2088,7 @@
final long interval = mConstants.getFullBackupIntervalMilliseconds();
final long appLatency = (timeSinceLast < interval) ? (interval - timeSinceLast) : 0;
final long latency = Math.max(transportMinLatency, appLatency);
- FullBackupJob.schedule(mUserId, mContext, latency, this);
+ FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
} else {
if (DEBUG_SCHEDULING) {
Slog.i(
@@ -2226,7 +2226,7 @@
addUserIdToLogMessage(
mUserId, "Deferring scheduled full backups in battery saver mode"));
}
- FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, this);
+ FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants);
return false;
}
@@ -2392,7 +2392,7 @@
+ "operation; rescheduling +" + latency));
}
final long deferTime = latency; // pin for the closure
- FullBackupJob.schedule(mUserId, mContext, deferTime, this);
+ FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
return false;
}
@@ -2495,7 +2495,7 @@
}
// ...and schedule a backup pass if necessary
- KeyValueBackupJob.schedule(mUserId, mContext, this);
+ KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
}
// Note: packageName is currently unused, but may be in the future
@@ -2730,7 +2730,7 @@
mUserId, "Not running backup while in battery save mode"));
}
// Try again in several hours.
- KeyValueBackupJob.schedule(mUserId, mContext, this);
+ KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
} else {
if (DEBUG) {
Slog.v(TAG, addUserIdToLogMessage(mUserId, "Scheduling immediate backup pass"));
@@ -3208,49 +3208,12 @@
}
}
- synchronized void setFrameworkSchedulingEnabled(boolean isEnabled) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
- "setFrameworkSchedulingEnabled");
-
- boolean wasEnabled = isFrameworkSchedulingEnabled();
- if (wasEnabled == isEnabled) return;
-
- Slog.i(TAG, addUserIdToLogMessage(mUserId,
- (isEnabled ? "Enabling" : "Disabling") + " backup scheduling"));
-
- final long oldId = Binder.clearCallingIdentity();
- try {
- // TODO(b/264889098): Consider at a later point if we should us a sentinel file as
- // setBackupEnabled.
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.BACKUP_SCHEDULING_ENABLED, isEnabled ? 1 : 0, mUserId);
-
- if (!isEnabled) {
- KeyValueBackupJob.cancel(mUserId, mContext);
- FullBackupJob.cancel(mUserId, mContext);
- } else {
- KeyValueBackupJob.schedule(mUserId, mContext, this);
- scheduleNextFullBackupJob(/* transportMinLatency */ 0);
- }
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
- }
-
- synchronized boolean isFrameworkSchedulingEnabled() {
- // By default scheduling is enabled
- final int defaultSetting = 1;
- int isEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.BACKUP_SCHEDULING_ENABLED, defaultSetting, mUserId);
- return isEnabled == 1;
- }
-
@VisibleForTesting
void updateStateOnBackupEnabled(boolean wasEnabled, boolean enable) {
synchronized (mQueueLock) {
if (enable && !wasEnabled && mSetupComplete) {
// if we've just been enabled, start scheduling backup passes
- KeyValueBackupJob.schedule(mUserId, mContext, this);
+ KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
scheduleNextFullBackupJob(0);
} else if (!enable) {
// No longer enabled, so stop running backups
@@ -4164,8 +4127,6 @@
pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
if (mBackupRunning) pw.println("Backup currently running");
pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running");
- pw.println("Framework scheduling is "
- + (isFrameworkSchedulingEnabled() ? "enabled" : "disabled"));
pw.println("Last backup pass started: " + mLastBackupPass
+ " (now = " + System.currentTimeMillis() + ')');
pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
index f399fe9..c5e912e 100644
--- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java
+++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
+import android.provider.Settings;
import android.util.Slog;
import com.android.server.backup.KeyValueBackupJob;
@@ -77,7 +78,7 @@
Slog.d(TAG, "Setup complete so starting backups");
}
KeyValueBackupJob.schedule(mUserBackupManagerService.getUserId(), mContext,
- mUserBackupManagerService);
+ mUserBackupManagerService.getConstants());
mUserBackupManagerService.scheduleNextFullBackupJob(0);
}
}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 41e8092..ca92b69 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -1246,7 +1246,7 @@
delay = 0;
}
KeyValueBackupJob.schedule(mBackupManagerService.getUserId(),
- mBackupManagerService.getContext(), delay, mBackupManagerService);
+ mBackupManagerService.getContext(), delay, mBackupManagerService.getConstants());
for (String packageName : mOriginalQueue) {
mBackupManagerService.dataChangedImpl(packageName);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 68722d2..661319f3 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -64,6 +64,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.pm.ApexManager;
import libcore.util.HexEncoding;
@@ -1266,10 +1267,15 @@
private String getOriginalApexPreinstalledLocation(String packageName,
String currentInstalledLocation) {
try {
+ // It appears that only apexd knows the preinstalled location, and it uses module name
+ // as the identifier instead of package name. Given the input is a package name, we
+ // need to covert to module name.
+ final String moduleName = ApexManager.getInstance().getApexModuleNameForPackageName(
+ packageName);
IApexService apexService = IApexService.Stub.asInterface(
Binder.allowBlocking(ServiceManager.waitForService("apexservice")));
for (ApexInfo info : apexService.getAllPackages()) {
- if (packageName.equals(info.moduleName)) {
+ if (moduleName.equals(info.moduleName)) {
return info.preinstalledModulePath;
}
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 274370e..b67e627 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -227,8 +227,8 @@
# ---------------------------
# NetworkStatsService.java
# ---------------------------
-51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
-51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+51100 netstats_mobile_sample (xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+51101 netstats_wifi_sample (xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
# ---------------------------
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 88492ed..35b5f1b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2010,7 +2010,7 @@
@Override
public void hasFeatures(IAccountManagerResponse response,
- Account account, String[] features, String opPackageName) {
+ Account account, String[] features, int userId, String opPackageName) {
int callingUid = Binder.getCallingUid();
mAppOpsManager.checkPackage(callingUid, opPackageName);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2018,12 +2018,22 @@
+ ", response " + response
+ ", features " + Arrays.toString(features)
+ ", caller's uid " + callingUid
+ + ", userId " + userId
+ ", pid " + Binder.getCallingPid());
}
Preconditions.checkArgument(account != null, "account cannot be null");
Preconditions.checkArgument(response != null, "response cannot be null");
Preconditions.checkArgument(features != null, "features cannot be null");
- int userId = UserHandle.getCallingUserId();
+
+ if (userId != UserHandle.getCallingUserId()
+ && callingUid != Process.SYSTEM_UID
+ && mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("User " + UserHandle.getCallingUserId()
+ + " trying to check account features for " + userId);
+ }
+
checkReadAccountsPermitted(callingUid, account.type, userId,
opPackageName);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eb10759..9752e8b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1443,6 +1443,9 @@
private boolean mWaitForDebugger = false;
@GuardedBy("this")
+ private boolean mSuspendUponWait = false;
+
+ @GuardedBy("this")
private boolean mDebugTransient = false;
@GuardedBy("this")
@@ -5011,9 +5014,15 @@
try {
int testMode = ApplicationThreadConstants.DEBUG_OFF;
if (mDebugApp != null && mDebugApp.equals(processName)) {
- testMode = mWaitForDebugger
- ? ApplicationThreadConstants.DEBUG_WAIT
- : ApplicationThreadConstants.DEBUG_ON;
+ if (mWaitForDebugger) {
+ if (mSuspendUponWait) {
+ testMode = ApplicationThreadConstants.DEBUG_SUSPEND;
+ } else {
+ testMode = ApplicationThreadConstants.DEBUG_WAIT;
+ }
+ } else {
+ testMode = ApplicationThreadConstants.DEBUG_ON;
+ }
app.setDebugging(true);
if (mDebugTransient) {
mDebugApp = mOrigDebugApp;
@@ -7160,6 +7169,11 @@
public void setDebugApp(String packageName, boolean waitForDebugger,
boolean persistent) {
+ setDebugApp(packageName, waitForDebugger, persistent, false);
+ }
+
+ private void setDebugApp(String packageName, boolean waitForDebugger,
+ boolean persistent, boolean suspendUponWait) {
enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
"setDebugApp()");
@@ -7185,6 +7199,7 @@
}
mDebugApp = packageName;
mWaitForDebugger = waitForDebugger;
+ mSuspendUponWait = suspendUponWait;
mDebugTransient = !persistent;
if (packageName != null) {
forceStopPackageLocked(packageName, -1, false, false, true, true,
@@ -17868,6 +17883,10 @@
setDebugApp(aInfo.processName, true, false);
}
+ if ((startFlags & ActivityManager.START_FLAG_DEBUG_SUSPEND) != 0) {
+ setDebugApp(aInfo.processName, true, false, true);
+ }
+
if ((startFlags & ActivityManager.START_FLAG_NATIVE_DEBUGGING) != 0) {
setNativeDebuggingAppLocked(aInfo.applicationInfo, aInfo.processName);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4b7857f..0a73eaa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -412,6 +412,8 @@
public boolean handleOption(String opt, ShellCommand cmd) {
if (opt.equals("-D")) {
mStartFlags |= ActivityManager.START_FLAG_DEBUG;
+ } else if (opt.equals("--suspend")) {
+ mStartFlags |= ActivityManager.START_FLAG_DEBUG_SUSPEND;
} else if (opt.equals("-N")) {
mStartFlags |= ActivityManager.START_FLAG_NATIVE_DEBUGGING;
} else if (opt.equals("-W")) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 9d7f53d..b1a01cc 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -397,11 +397,12 @@
+ ": " + r);
mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
+ final boolean assumeDelivered = false;
thread.scheduleReceiverList(mReceiverBatch.manifestReceiver(
prepareReceiverIntent(r.intent, r.curFilteredExtras),
r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
- r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
- app.mState.getReportedProcState()));
+ r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
+ r.userId, app.mState.getReportedProcState()));
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " DELIVERED for app " + app);
started = true;
@@ -735,9 +736,10 @@
// If we have an app thread, do the call through that so it is
// correctly ordered with other one-way calls.
try {
+ final boolean assumeDelivered = !ordered;
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
receiver, intent, resultCode,
- data, extras, ordered, sticky, sendingUser,
+ data, extras, ordered, sticky, assumeDelivered, sendingUser,
app.mState.getReportedProcState()));
} catch (RemoteException ex) {
// Failed to call into the process. It's either dying or wedged. Kill it gently.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 87d0bc7..99e2ac7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -508,16 +508,7 @@
mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
}
- if (waitingFor) {
- mWaitingFor.removeIf((pair) -> {
- if (pair.first.getAsBoolean()) {
- pair.second.countDown();
- return true;
- } else {
- return false;
- }
- });
- }
+ checkAndRemoveWaitingFor();
traceEnd(cookie);
}
@@ -897,21 +888,26 @@
return true;
}
+ final boolean assumeDelivered = isAssumedDelivered(r, index);
if (receiver instanceof BroadcastFilter) {
batch.schedule(((BroadcastFilter) receiver).receiverList.receiver,
receiverIntent, r.resultCode, r.resultData, r.resultExtras,
- r.ordered, r.initialSticky, r.userId,
+ r.ordered, r.initialSticky, assumeDelivered, r.userId,
app.mState.getReportedProcState(), r, index);
// TODO: consider making registered receivers of unordered
// broadcasts report results to detect ANRs
- if (!r.ordered) {
+ if (assumeDelivered) {
batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
return true;
}
} else {
batch.schedule(receiverIntent, ((ResolveInfo) receiver).activityInfo,
- null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
- app.mState.getReportedProcState(), r, index);
+ null, r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
+ r.userId, app.mState.getReportedProcState(), r, index);
+ if (assumeDelivered) {
+ batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
+ return true;
+ }
}
return false;
@@ -1000,7 +996,8 @@
* Return true if this receiver should be assumed to have been delivered.
*/
private boolean isAssumedDelivered(BroadcastRecord r, int index) {
- return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered;
+ return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered
+ && (r.resultTo == null);
}
/**
@@ -1059,10 +1056,11 @@
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app, OOM_ADJ_REASON_FINISH_RECEIVER);
try {
+ final boolean assumeDelivered = true;
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
r.resultTo, r.intent,
r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
- r.userId, app.mState.getReportedProcState()));
+ assumeDelivered, r.userId, app.mState.getReportedProcState()));
} catch (RemoteException e) {
final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
logw(msg);
@@ -1181,6 +1179,9 @@
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
}
+ // Given that a receiver just finished, check if the "waitingFor" conditions are met.
+ checkAndRemoveWaitingFor();
+
if (early) {
// This is an early receiver that was transmitted as part of a group. The delivery
// state has been updated but don't make any further decisions.
@@ -1509,7 +1510,7 @@
waitFor(() -> isBeyondBarrierLocked(now, pw));
}
- public void waitFor(@NonNull BooleanSupplier condition) {
+ private void waitFor(@NonNull BooleanSupplier condition) {
final CountDownLatch latch = new CountDownLatch(1);
synchronized (mService) {
mWaitingFor.add(Pair.create(condition, latch));
@@ -1531,6 +1532,19 @@
}
}
+ private void checkAndRemoveWaitingFor() {
+ if (!mWaitingFor.isEmpty()) {
+ mWaitingFor.removeIf((pair) -> {
+ if (pair.first.getAsBoolean()) {
+ pair.second.countDown();
+ return true;
+ } else {
+ return false;
+ }
+ });
+ }
+ }
+
@Override
public void forceDelayBroadcastDelivery(@NonNull String targetPackage,
long delayedDurationMs) {
diff --git a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
index a826458..226647c 100644
--- a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
+++ b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
@@ -19,8 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ReceiverInfo;
-import android.content.Intent;
import android.content.IIntentReceiver;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
import android.os.Bundle;
@@ -171,12 +171,13 @@
// Add a ReceiverInfo for a registered receiver.
void schedule(@Nullable IIntentReceiver receiver, Intent intent,
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
- boolean sticky, int sendingUser, int processState,
+ boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
@Nullable BroadcastRecord r, int index) {
ReceiverInfo ri = new ReceiverInfo();
ri.intent = intent;
ri.data = data;
ri.extras = extras;
+ ri.assumeDelivered = assumeDelivered;
ri.sendingUser = sendingUser;
ri.processState = processState;
ri.resultCode = resultCode;
@@ -190,12 +191,13 @@
// Add a ReceiverInfo for a manifest receiver.
void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
@Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
- @Nullable Bundle extras, boolean sync, int sendingUser, int processState,
- @Nullable BroadcastRecord r, int index) {
+ @Nullable Bundle extras, boolean sync, boolean assumeDelivered, int sendingUser,
+ int processState, @Nullable BroadcastRecord r, int index) {
ReceiverInfo ri = new ReceiverInfo();
ri.intent = intent;
ri.data = data;
ri.extras = extras;
+ ri.assumeDelivered = assumeDelivered;
ri.sendingUser = sendingUser;
ri.processState = processState;
ri.resultCode = resultCode;
@@ -214,10 +216,10 @@
*/
ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
@Nullable Intent intent, int resultCode, @Nullable String data,
- @Nullable Bundle extras, boolean ordered, boolean sticky,
+ @Nullable Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
int sendingUser, int processState) {
reset();
- schedule(receiver, intent, resultCode, data, extras, ordered, sticky,
+ schedule(receiver, intent, resultCode, data, extras, ordered, sticky, assumeDelivered,
sendingUser, processState, null, 0);
return receivers();
}
@@ -225,9 +227,9 @@
ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
@Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
- int sendingUser, int processState) {
+ boolean assumeDelivered, int sendingUser, int processState) {
reset();
- schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync,
+ schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync, assumeDelivered,
sendingUser, processState, null, 0);
return receivers();
}
@@ -255,6 +257,7 @@
n.intent = r.intent;
n.data = r.data;
n.extras = r.extras;
+ n.assumeDelivered = r.assumeDelivered;
n.sendingUser = r.sendingUser;
n.processState = r.processState;
n.resultCode = r.resultCode;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dbd58eb..81655cf 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2620,7 +2620,7 @@
}
state.setCurRawAdj(adj);
-
+ adj = psr.modifyRawOomAdj(adj);
if (adj > state.getMaxAdj()) {
adj = state.getMaxAdj();
if (adj <= PERCEPTIBLE_LOW_APP_ADJ) {
@@ -2650,7 +2650,7 @@
// it when computing the final cached adj later. Note that we don't need to
// worry about this for max adj above, since max adj will always be used to
// keep it out of the cached vaues.
- state.setCurAdj(psr.modifyRawOomAdj(adj));
+ state.setCurAdj(adj);
state.setCurCapability(capability);
state.setCurrentSchedulingGroup(schedGroup);
state.setCurProcState(procState);
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index 62fd6e9..082e8e0 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -47,12 +47,12 @@
@Override
public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
- int resultCode, String data, Bundle extras, boolean sync, int sendingUser,
- int processState) {
+ int resultCode, String data, Bundle extras, boolean ordered, boolean assumeDelivered,
+ int sendingUser, int processState) {
mHandler.post(() -> {
try {
- mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras, sync,
- sendingUser, processState);
+ mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras,
+ ordered, assumeDelivered, sendingUser, processState);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -61,12 +61,12 @@
@Override
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser,
- int processState) {
+ String data, Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
+ int sendingUser, int processState) {
mHandler.post(() -> {
try {
mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras,
- ordered, sticky, sendingUser, processState);
+ ordered, sticky, assumeDelivered, sendingUser, processState);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -79,11 +79,11 @@
ReceiverInfo r = info.get(i);
if (r.registered) {
scheduleRegisteredReceiver(r.receiver, r.intent,
- r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+ r.resultCode, r.data, r.extras, r.ordered, r.sticky, r.assumeDelivered,
r.sendingUser, r.processState);
} else {
scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
- r.resultCode, r.data, r.extras, r.sync,
+ r.resultCode, r.data, r.extras, r.sync, r.assumeDelivered,
r.sendingUser, r.processState);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 58ddd9c..c794b04 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3733,18 +3733,6 @@
}
}
- // TODO enforce MODIFY_AUDIO_SYSTEM_SETTINGS when defined
- private void enforceModifyAudioRoutingOrSystemSettingsPermission() {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- != PackageManager.PERMISSION_GRANTED
- /*&& mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
- != PackageManager.PERMISSION_DENIED*/) {
- throw new SecurityException(
- "Missing MODIFY_AUDIO_ROUTING or MODIFY_AUDIO_SYSTEM_SETTINGS permission");
- }
- }
-
private void enforceAccessUltrasoundPermission() {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
!= PackageManager.PERMISSION_GRANTED) {
@@ -3785,12 +3773,13 @@
super.setVolumeIndexForAttributes_enforcePermission();
Objects.requireNonNull(attr, "attr must not be null");
- final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+ int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+ attr, /* fallbackOnDefault= */false);
if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
Log.e(TAG, ": no volume group found for attributes " + attr.toString());
return;
}
- final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+ VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
index/*val1*/, flags/*val2*/, callingPackage));
@@ -3798,7 +3787,7 @@
vgs.setVolumeIndex(index, flags);
// For legacy reason, propagate to all streams associated to this volume group
- for (final int groupedStream : vgs.getLegacyStreamTypes()) {
+ for (int groupedStream : vgs.getLegacyStreamTypes()) {
try {
ensureValidStreamType(groupedStream);
} catch (IllegalArgumentException e) {
@@ -3830,7 +3819,9 @@
super.getVolumeIndexForAttributes_enforcePermission();
Objects.requireNonNull(attr, "attr must not be null");
- final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+ final int volumeGroup =
+ AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+ attr, /* fallbackOnDefault= */false);
if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
throw new IllegalArgumentException("No volume group for attributes " + attr);
}
@@ -3856,13 +3847,16 @@
return AudioSystem.getMinVolumeIndexForAttributes(attr);
}
+ @Override
+ @android.annotation.EnforcePermission(anyOf =
+ {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SYSTEM_SETTINGS"})
/** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
* Part of service interface, check permissions and parameters here
* Note calling package is for logging purposes only, not to be trusted
*/
public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
@NonNull String callingPackage) {
- enforceModifyAudioRoutingOrSystemSettingsPermission();
+ super.setDeviceVolume_enforcePermission();
Objects.requireNonNull(vi);
Objects.requireNonNull(ada);
Objects.requireNonNull(callingPackage);
@@ -4380,31 +4374,6 @@
sendVolumeUpdate(streamType, oldIndex, index, flags, device);
}
-
-
- private int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
- Objects.requireNonNull(attributes, "attributes must not be null");
- int volumeGroupId = getVolumeGroupIdForAttributesInt(attributes);
- if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
- return volumeGroupId;
- }
- // The default volume group is the one hosted by default product strategy, i.e.
- // supporting Default Attributes
- return getVolumeGroupIdForAttributesInt(AudioProductStrategy.getDefaultAttributes());
- }
-
- private int getVolumeGroupIdForAttributesInt(@NonNull AudioAttributes attributes) {
- Objects.requireNonNull(attributes, "attributes must not be null");
- for (final AudioProductStrategy productStrategy :
- AudioProductStrategy.getAudioProductStrategies()) {
- int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
- if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
- return volumeGroupId;
- }
- }
- return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
- }
-
private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo,
int index) {
VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType);
@@ -4437,7 +4406,6 @@
}
}
-
// No ringer or zen muted stream volumes can be changed unless it'll exit dnd
private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) {
switch (mNm.getZenMode()) {
@@ -4828,12 +4796,15 @@
}
}
+ @Override
+ @android.annotation.EnforcePermission(anyOf =
+ {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SYSTEM_SETTINGS"})
/**
* @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes)
*/
public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
@NonNull AudioDeviceAttributes ada, @NonNull String callingPackage) {
- enforceModifyAudioRoutingOrSystemSettingsPermission();
+ super.getDeviceVolume_enforcePermission();
Objects.requireNonNull(vi);
Objects.requireNonNull(ada);
Objects.requireNonNull(callingPackage);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index dcc98e1..5b696c2 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -104,6 +104,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.config.appcloning.AppCloningDeviceConfigHelper;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
@@ -264,6 +265,8 @@
private final SyncLogger mLogger;
+ private final AppCloningDeviceConfigHelper mAppCloningDeviceConfigHelper;
+
private boolean isJobIdInUseLockedH(int jobId, List<JobInfo> pendingJobs) {
for (int i = 0, size = pendingJobs.size(); i < size; i++) {
JobInfo job = pendingJobs.get(i);
@@ -627,6 +630,7 @@
}, mSyncHandler);
mConstants = new SyncManagerConstants(context);
+ mAppCloningDeviceConfigHelper = AppCloningDeviceConfigHelper.getInstance(context);
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
@@ -828,6 +832,18 @@
}
/**
+ * Check whether the feature flag controlling contacts sharing for clone profile is set. If
+ * true, the contact syncs for clone profile should be disabled.
+ *
+ * @return true/false if contact sharing is enabled/disabled
+ */
+ protected boolean isContactSharingAllowedForCloneProfile() {
+ // TODO(b/253449368): This method should also check for the config controlling
+ // all app-cloning features.
+ return mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks();
+ }
+
+ /**
* Check if account sync should be disabled for the given user and provider.
* @param userInfo
* @param providerName
@@ -836,7 +852,9 @@
*/
@VisibleForTesting
protected boolean shouldDisableSyncForUser(UserInfo userInfo, String providerName) {
- if (userInfo == null || providerName == null) return false;
+ if (userInfo == null || providerName == null || !isContactSharingAllowedForCloneProfile()) {
+ return false;
+ }
return providerName.equals(ContactsContract.AUTHORITY)
&& !areContactWritesEnabledForUser(userInfo);
}
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index e21895a..35434b7 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -136,14 +136,14 @@
@ImeVisibilityStateComputer.VisibilityState int state, int reason) {
switch (state) {
case STATE_SHOW_IME:
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// Send to window manager to show IME after IME layout finishes.
mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
break;
case STATE_HIDE_IME:
if (mService.mCurFocusedWindowClient != null) {
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// IMMS only knows of focused window, not the actual IME target.
// e.g. it isn't aware of any window that has both
@@ -154,7 +154,7 @@
mWindowManagerInternal.hideIme(windowToken,
mService.mCurFocusedWindowClient.mSelfReportedDisplayId, statsToken);
} else {
- ImeTracker.get().onFailed(statsToken,
+ ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
}
break;
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 795e4bf..10c16b6 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -182,10 +182,10 @@
*/
boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) {
if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
return false;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
mRequestedShowExplicitly = true;
mShowForced = true;
@@ -206,15 +206,15 @@
if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mRequestedShowExplicitly || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
return false;
}
if (mShowForced && (hideFlags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return false;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return true;
}
@@ -380,7 +380,12 @@
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
- // Do nothing.
+ // Do nothing but preserving the last IME requested visibility state.
+ final ImeTargetWindowState lastState =
+ getWindowStateOrNull(mService.mLastImeTargetWindow);
+ if (lastState != null) {
+ state.setRequestedImeVisible(lastState.mRequestedImeVisible);
+ }
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if (isForwardNavigation) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 187de930..ba9e280 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -469,11 +469,11 @@
@GuardedBy("ImfLock.class")
private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
- if (getCurIntent() == null || conn == null) {
+ if (mCurIntent == null || conn == null) {
Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
return false;
}
- return mContext.bindServiceAsUser(getCurIntent(), conn, flags,
+ return mContext.bindServiceAsUser(mCurIntent, conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index a94c90c..ce3abfd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1958,6 +1958,19 @@
}
@BinderThread
+ @Nullable
+ @Override
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ }
+ synchronized (ImfLock.class) {
+ return queryDefaultInputMethodForUserIdLocked(userId);
+ }
+ }
+
+ @BinderThread
@NonNull
@Override
public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
@@ -2277,7 +2290,7 @@
mCurClient.mSessionRequestedForAccessibility = false;
mCurClient = null;
mCurVirtualDisplayToScreenMatrix = null;
- ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
mCurStatsToken = null;
mMenuController.hideInputMethodMenuLocked();
@@ -3263,7 +3276,8 @@
"InputMethodManagerService#showSoftInput");
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -3336,15 +3350,16 @@
@BinderThread
@Override
public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
- Objects.requireNonNull(windowToken, "windowToken must not be null");
- synchronized (ImfLock.class) {
- if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
- return;
+ Binder.withCleanCallingIdentity(() -> {
+ Objects.requireNonNull(windowToken, "windowToken must not be null");
+ synchronized (ImfLock.class) {
+ if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
+ return;
+ }
+ mCurPerceptible = perceptible;
+ updateSystemUiLocked();
}
- mCurPerceptible = perceptible;
- Binder.withCleanCallingIdentity(() ->
- updateSystemUiLocked(mImeWindowVis, mBackDisposition));
- }
+ });
}
@GuardedBy("ImfLock.class")
@@ -3363,7 +3378,7 @@
// TODO(b/261565259): to avoid using null, add package name in ClientState
final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
final int uid = mCurClient != null ? mCurClient.mUid : -1;
- statsToken = ImeTracker.get().onRequestShow(packageName, uid,
+ statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
}
@@ -3372,19 +3387,19 @@
}
if (!mSystemReady) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
return false;
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
mVisibilityStateComputer.requestImeVisibility(windowToken, true);
// Ensure binding the connection when IME is going to show.
mBindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = getCurMethodLocked();
- ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
if (curMethod != null) {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
mCurStatsToken = null;
if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
@@ -3395,7 +3410,7 @@
mVisibilityStateComputer.setInputShown(true);
return true;
} else {
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
mCurStatsToken = statsToken;
}
return false;
@@ -3411,9 +3426,10 @@
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
if (isInputShown()) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
} else {
- ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.forLogging().onCancelled(statsToken,
ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
}
return false;
@@ -3446,7 +3462,7 @@
} else {
uid = -1;
}
- statsToken = ImeTracker.get().onRequestHide(packageName, uid,
+ statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
}
@@ -3472,15 +3488,15 @@
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason);
} else {
- ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
+ ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
}
mBindingController.setCurrentMethodNotVisible();
mVisibilityStateComputer.clearImeShowFlags();
// Cancel existing statsToken for show IME as we got a hide request.
- ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
mCurStatsToken = null;
return shouldHideSoftInput;
}
@@ -3752,16 +3768,16 @@
// be made before input is started in it.
final ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
if (!isImeClientFocused(mCurFocusedWindow, cs)) {
Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
return false;
}
}
- ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
return true;
}
@@ -4538,7 +4554,8 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
- ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
return;
}
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(windowToken);
@@ -5288,6 +5305,53 @@
return mCurrentSubtype;
}
+ /**
+ * Returns the default {@link InputMethodInfo} for the specific userId.
+ * @param userId user ID to query.
+ */
+ @GuardedBy("ImfLock.class")
+ private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
+ final String imeId = mSettings.getSelectedInputMethodForUser(userId);
+ if (TextUtils.isEmpty(imeId)) {
+ Slog.e(TAG, "No default input method found for userId " + userId);
+ return null;
+ }
+
+ InputMethodInfo curInputMethodInfo;
+ if (userId == mSettings.getCurrentUserId()
+ && (curInputMethodInfo = mMethodMap.get(imeId)) != null) {
+ // clone the InputMethodInfo before returning.
+ return new InputMethodInfo(curInputMethodInfo);
+ }
+
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ Context userAwareContext =
+ mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+
+ final int flags = PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_AUTO;
+ final List<ResolveInfo> services =
+ userAwareContext.getPackageManager().queryIntentServicesAsUser(
+ new Intent(InputMethod.SERVICE_INTERFACE),
+ PackageManager.ResolveInfoFlags.of(flags),
+ userId);
+ for (ResolveInfo ri : services) {
+ final String imeIdResolved = InputMethodInfo.computeId(ri);
+ if (imeId.equals(imeIdResolved)) {
+ try {
+ return new InputMethodInfo(
+ userAwareContext, ri, additionalSubtypeMap.get(imeId));
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Unable to load input method " + imeId, e);
+ }
+ }
+ }
+ // we didn't find the InputMethodInfo for imeId. This shouldn't happen.
+ Slog.e(TAG, "Error while locating input method info for imeId: " + imeId);
+ return null;
+ }
private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) {
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index be99bfb..559eb53 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -330,11 +330,17 @@
@Nullable
private String getString(@NonNull String key, @Nullable String defaultValue) {
+ return getStringForUser(key, defaultValue, mCurrentUserId);
+ }
+
+ @Nullable
+ private String getStringForUser(
+ @NonNull String key, @Nullable String defaultValue, @UserIdInt int userId) {
final String result;
if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
result = mCopyOnWriteDataStore.get(key);
} else {
- result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
+ result = Settings.Secure.getStringForUser(mResolver, key, userId);
}
return result != null ? result : defaultValue;
}
@@ -741,6 +747,16 @@
return imi;
}
+ @Nullable
+ String getSelectedInputMethodForUser(@UserIdInt int userId) {
+ final String imi =
+ getStringForUser(Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedInputMethodForUserStr: " + imi);
+ }
+ return imi;
+ }
+
void putDefaultVoiceInputMethod(String imeId) {
if (DEBUG) {
Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index b4c9596..1cc958b 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -202,6 +202,9 @@
@Override
public void onUserStarting(TargetUser user) {
mUserInfoHelper.onUserStarted(user.getUserIdentifier());
+
+ // log location enabled state on start to minimize coverage loss
+ mService.logLocationEnabledState();
}
@Override
@@ -553,6 +556,7 @@
}
EVENT_LOG.logLocationEnabled(userId, enabled);
+ logLocationEnabledState();
Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -563,6 +567,20 @@
refreshAppOpsRestrictions(userId);
}
+ private void logLocationEnabledState() {
+ boolean locationEnabled = false;
+ // Location setting is considered on if it is enabled for any one user
+ int[] runningUserIds = mInjector.getUserInfoHelper().getRunningUserIds();
+ for (int userId : runningUserIds) {
+ locationEnabled = mInjector.getSettingsHelper().isLocationEnabled(userId);
+ if (locationEnabled) {
+ break;
+ }
+ }
+ mInjector.getLocationUsageLogger()
+ .logLocationEnabledStateChanged(locationEnabled);
+ }
+
@Override
public int getGnssYearOfHardware() {
return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssYearOfHardware();
diff --git a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
index af21bcb..a9701b3 100644
--- a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
+++ b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
@@ -122,6 +122,13 @@
}
}
+ /**
+ * Log a location enabled state change event.
+ */
+ public synchronized void logLocationEnabledStateChanged(boolean enabled) {
+ FrameworkStatsLog.write(FrameworkStatsLog.LOCATION_ENABLED_STATE_CHANGED, enabled);
+ }
+
private static int bucketizeProvider(String provider) {
if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
return LocationStatsEnums.PROVIDER_NETWORK;
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
new file mode 100644
index 0000000..a792498
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+ "presubmit": [
+ {
+ "name": "MediaProjectionTests",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 5ab9f38..69ea559 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -108,7 +108,7 @@
@VisibleForTesting
static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
@VisibleForTesting
- static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 50000;
+ static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000;
private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index f85d6af..5c4c7c9 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -189,13 +189,14 @@
void onPackagesUpdated(ArraySet<String> updatedPackages);
}
- public BackgroundDexOptService(
- Context context, DexManager dexManager, PackageManagerService pm) {
+ public BackgroundDexOptService(Context context, DexManager dexManager, PackageManagerService pm)
+ throws LegacyDexoptDisabledException {
this(new Injector(context, dexManager, pm));
}
@VisibleForTesting
- public BackgroundDexOptService(Injector injector) {
+ public BackgroundDexOptService(Injector injector) throws LegacyDexoptDisabledException {
+ Installer.checkLegacyDexoptDisabled();
mInjector = injector;
mDexOptHelper = mInjector.getDexOptHelper();
LocalServices.addService(BackgroundDexOptService.class, this);
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 9da319d..53e23e0 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -996,6 +996,8 @@
artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */,
Runnable::run, new DexoptDoneHandler(Objects.requireNonNull(pm)));
LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
+
+ artManager.scheduleBackgroundDexoptJob();
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 93a119c..7f7a234 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -291,8 +291,8 @@
rollbackTimeoutIntent.putExtra(
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID,
sessionId);
- rollbackTimeoutIntent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ rollbackTimeoutIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_FOREGROUND);
mPm.mContext.sendBroadcastAsUser(rollbackTimeoutIntent, UserHandle.SYSTEM,
android.Manifest.permission.PACKAGE_ROLLBACK_AGENT);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9cc0334..dfd305a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -789,8 +789,10 @@
final ArtManagerService mArtManagerService;
+ // TODO(b/260124949): Remove these.
final PackageDexOptimizer mPackageDexOptimizer;
- final BackgroundDexOptService mBackgroundDexOptService;
+ @Nullable
+ final BackgroundDexOptService mBackgroundDexOptService; // null when ART Service is in use.
// DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
// is used by other apps).
private final DexManager mDexManager;
@@ -1567,7 +1569,16 @@
new DefaultSystemWrapper(),
LocalServices::getService,
context::getSystemService,
- (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm),
+ (i, pm) -> {
+ if (useArtService()) {
+ return null;
+ }
+ try {
+ return new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
+ },
(i, pm) -> IBackupManager.Stub.asInterface(ServiceManager.getService(
Context.BACKUP_SERVICE)),
(i, pm) -> new SharedLibrariesImpl(pm, i),
@@ -4287,11 +4298,14 @@
}
});
- // TODO(b/251903639): Call into ART Service.
- try {
- mBackgroundDexOptService.systemReady();
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ if (!useArtService()) {
+ // The background dexopt job is scheduled in DexOptHelper.initializeArtManagerLocal when
+ // ART Service is in use.
+ try {
+ mBackgroundDexOptService.systemReady();
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
}
// Prune unused static shared libraries which have been cached a period of time
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index eb033cb..13549f5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.backup.IBackupManager;
import android.content.ComponentName;
@@ -138,7 +139,8 @@
private final Singleton<DomainVerificationManagerInternal>
mDomainVerificationManagerInternalProducer;
private final Singleton<Handler> mHandlerProducer;
- private final Singleton<BackgroundDexOptService> mBackgroundDexOptService;
+ private final Singleton<BackgroundDexOptService>
+ mBackgroundDexOptService; // TODO(b/260124949): Remove this.
private final Singleton<IBackupManager> mIBackupManager;
private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer;
private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer;
@@ -408,6 +410,7 @@
return getLocalService(ActivityManagerInternal.class);
}
+ @Nullable
public BackgroundDexOptService getBackgroundDexOptService() {
return mBackgroundDexOptService.get(this, mPackageManager);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 0c617ae..08ff51d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -106,7 +106,7 @@
public boolean isEngBuild;
public boolean isUserDebugBuild;
public int sdkInt = Build.VERSION.SDK_INT;
- public BackgroundDexOptService backgroundDexOptService;
+ public @Nullable BackgroundDexOptService backgroundDexOptService;
public final String incrementalVersion = Build.VERSION.INCREMENTAL;
public BroadcastHelper broadcastHelper;
public AppDataHelper appDataHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 12841a4..0685435 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2875,6 +2875,8 @@
newUserType = UserManager.USER_TYPE_FULL_DEMO;
} else if ("--ephemeral".equals(opt)) {
flags |= UserInfo.FLAG_EPHEMERAL;
+ } else if ("--for-testing".equals(opt)) {
+ flags |= UserInfo.FLAG_FOR_TESTING;
} else if ("--pre-create-only".equals(opt)) {
preCreateOnly = true;
} else if ("--user-type".equals(opt)) {
@@ -4269,8 +4271,8 @@
pw.println(" list users");
pw.println(" Lists the current users.");
pw.println("");
- pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]");
- pw.println(" [--guest] [--pre-create-only] [--user-type USER_TYPE] USER_NAME");
+ pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--guest]");
+ pw.println(" [--user-type USER_TYPE] [--ephemeral] [--for-testing] [--pre-create-only] USER_NAME");
pw.println(" Create a new user with the given USER_NAME, printing the new user identifier");
pw.println(" of the user.");
// TODO(b/142482943): Consider fetching the list of user types from UMS.
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 7b15e76..1787116 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -513,4 +513,20 @@
* @see UserManager#isMainUser()
*/
public abstract @UserIdInt int getMainUserId();
+
+ /**
+ * Returns the id of the user which should be in the foreground after boot completes.
+ *
+ * <p>If a boot user has been provided by calling {@link UserManager#setBootUser}, the
+ * returned value will be whatever was specified, as long as that user exists and can be
+ * switched to.
+ *
+ * <p>Otherwise, in {@link UserManager#isHeadlessSystemUserMode() headless system user mode},
+ * this will be the user who was last in the foreground on this device. If there is no
+ * switchable user on the device, a new user will be created and its id will be returned.
+ *
+ * <p>In non-headless system user mode, the return value will be {@link UserHandle#USER_SYSTEM}.
+ */
+ public abstract @UserIdInt int getBootUser()
+ throws UserManager.CheckedUserOperationException;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a8cf8cb..175c11d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,6 +21,7 @@
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
+import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
import android.Manifest;
import android.accounts.Account;
@@ -31,7 +32,6 @@
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
@@ -44,6 +44,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -127,11 +128,8 @@
import com.android.server.LockGuard;
import com.android.server.SystemService;
import com.android.server.am.UserState;
-import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
-import com.android.server.pm.UserManagerInternal.UserStartMode;
-import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -254,7 +252,8 @@
| UserInfo.FLAG_RESTRICTED
| UserInfo.FLAG_GUEST
| UserInfo.FLAG_DEMO
- | UserInfo.FLAG_FULL;
+ | UserInfo.FLAG_FULL
+ | UserInfo.FLAG_FOR_TESTING;
@VisibleForTesting
static final int MIN_USER_ID = UserHandle.MIN_SECONDARY_USER_ID;
@@ -637,6 +636,9 @@
private final UserVisibilityMediator mUserVisibilityMediator;
+ @GuardedBy("mUsersLock")
+ private @UserIdInt int mBootUser = UserHandle.USER_NULL;
+
private static UserManagerService sInstance;
public static UserManagerService getInstance() {
@@ -936,6 +938,26 @@
}
@Override
+ public void setBootUser(@UserIdInt int userId) {
+ checkCreateUsersPermission("Set boot user");
+ synchronized (mUsersLock) {
+ // TODO(b/263381643): Change to EventLog.
+ Slogf.i(LOG_TAG, "setBootUser %d", userId);
+ mBootUser = userId;
+ }
+ }
+
+ @Override
+ public @UserIdInt int getBootUser() {
+ checkCreateUsersPermission("Get boot user");
+ try {
+ return mLocalService.getBootUser();
+ } catch (UserManager.CheckedUserOperationException e) {
+ throw e.toServiceSpecificException();
+ }
+ }
+
+ @Override
public int getPreviousFullUserToEnterForeground() {
checkQueryOrCreateUsersPermission("get previous user");
int previousUser = UserHandle.USER_NULL;
@@ -1569,6 +1591,8 @@
Slog.w(LOG_TAG, "System user instantiated at least " + number + " times");
}
name = getOwnerName();
+ } else if (orig.isMain()) {
+ name = getOwnerName();
} else if (orig.isGuest()) {
name = getGuestName();
}
@@ -4535,7 +4559,7 @@
UserHandle.USER_NULL, null);
if (userInfo == null) {
- throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ throw new ServiceSpecificException(USER_OPERATION_ERROR_UNKNOWN);
}
} catch (UserManager.CheckedUserOperationException e) {
throw e.toServiceSpecificException();
@@ -4664,7 +4688,7 @@
if (parent == null) {
throwCheckedUserOperationException(
"Cannot find user data for parent user " + parentId,
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ USER_OPERATION_ERROR_UNKNOWN);
}
}
if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
@@ -4692,7 +4716,7 @@
&& !isCreationOverrideEnabled()) {
throwCheckedUserOperationException(
"Cannot add restricted profile - parent user must be system",
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ USER_OPERATION_ERROR_UNKNOWN);
}
userId = getNextAvailableId();
@@ -5598,29 +5622,24 @@
// Also, add the UserHandle for mainline modules which can't use the @hide
// EXTRA_USER_HANDLE.
removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
- mContext.sendOrderedBroadcastAsUser(removedIntent, UserHandle.ALL,
- android.Manifest.permission.MANAGE_USERS,
-
- new BroadcastReceiver() {
+ getActivityManagerInternal().broadcastIntentWithCallback(removedIntent,
+ new IIntentReceiver.Stub() {
@Override
- public void onReceive(Context context, Intent intent) {
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
if (DBG) {
Slog.i(LOG_TAG,
"USER_REMOVED broadcast sent, cleaning up user data "
- + userId);
+ + userId);
}
- new Thread() {
- @Override
- public void run() {
- LocalServices.getService(ActivityManagerInternal.class)
- .onUserRemoved(userId);
- removeUserState(userId);
- }
- }.start();
+ new Thread(() -> {
+ getActivityManagerInternal().onUserRemoved(userId);
+ removeUserState(userId);
+ }).start();
}
},
-
- null, Activity.RESULT_OK, null, null);
+ new String[] {android.Manifest.permission.MANAGE_USERS},
+ UserHandle.USER_ALL, null, null, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6459,6 +6478,9 @@
if (DBG_ALLOCATION) {
pw.println(" System user allocations: " + mUser0Allocations.get());
}
+ synchronized (mUsersLock) {
+ pw.println(" Boot user: " + mBootUser);
+ }
pw.println();
pw.println("Number of listeners for");
@@ -6651,6 +6673,18 @@
return mLocalService.isUserInitialized(userId);
}
+ /**
+ * Creates a new user, intended to be the initial user on a device in headless system user mode.
+ */
+ private UserInfo createInitialUserForHsum() throws UserManager.CheckedUserOperationException {
+ final int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
+
+ // Null name will be replaced with "Owner" on-demand to allow for localisation.
+ return createUserInternalUnchecked(/* name= */ null, UserManager.USER_TYPE_FULL_SECONDARY,
+ flags, UserHandle.USER_NULL, /* preCreate= */ false,
+ /* disallowedPackages= */ null, /* token= */ null);
+ }
+
private class LocalService extends UserManagerInternal {
@Override
public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId,
@@ -7094,6 +7128,56 @@
return getMainUserIdUnchecked();
}
+ @Override
+ public @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException {
+ synchronized (mUsersLock) {
+ // TODO(b/242195409): On Automotive, block if boot user not provided.
+ if (mBootUser != UserHandle.USER_NULL) {
+ final UserData userData = mUsers.get(mBootUser);
+ if (userData != null && userData.info.supportsSwitchToByUser()) {
+ Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
+ return mBootUser;
+ } else {
+ Slogf.w(LOG_TAG,
+ "Provided boot user cannot be switched to: %d", mBootUser);
+ }
+ }
+ }
+
+ if (isHeadlessSystemUserMode()) {
+ // Return the previous foreground user, if there is one.
+ final int previousUser = getPreviousFullUserToEnterForeground();
+ if (previousUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+ return previousUser;
+ }
+ // No previous user. Return the first switchable user if there is one.
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData userData = mUsers.valueAt(i);
+ if (userData.info.supportsSwitchToByUser()) {
+ int firstSwitchable = userData.info.id;
+ Slogf.i(LOG_TAG,
+ "Boot user is first switchable user %d", firstSwitchable);
+ return firstSwitchable;
+ }
+ }
+ }
+ // No switchable users. Create the initial user.
+ final UserInfo newInitialUser = createInitialUserForHsum();
+ if (newInitialUser == null) {
+ throw new UserManager.CheckedUserOperationException(
+ "Initial user creation failed", USER_OPERATION_ERROR_UNKNOWN);
+ }
+ Slogf.i(LOG_TAG,
+ "No switchable users. Boot user is new user %d", newInitialUser.id);
+ return newInitialUser.id;
+ }
+ // Not HSUM, return system user.
+ return UserHandle.USER_SYSTEM;
+ }
+
} // class LocalService
@@ -7113,7 +7197,7 @@
+ restriction + " is enabled.";
Slog.w(LOG_TAG, errorMessage);
throw new UserManager.CheckedUserOperationException(errorMessage,
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
+ USER_OPERATION_ERROR_UNKNOWN);
}
}
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index a54f526..8ec6241 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -233,7 +233,8 @@
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID,
mSessionId);
enableRollbackIntent.setType(PACKAGE_MIME_TYPE);
- enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_RECEIVER_FOREGROUND);
// Allow the broadcast to be sent before boot complete.
// This is needed when committing the apk part of a staged
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index d622fd7..f9482dd 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -311,6 +311,24 @@
private final RailStats mTmpRailStats = new RailStats();
/**
+ * Estimate UID modem power usage based on their estimated mobile radio active time.
+ */
+ public static final int PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME = 1;
+ /**
+ * Estimate UID modem power consumption by proportionally attributing estimated Rx and Tx
+ * power consumption individually.
+ * ModemActivityInfo must be available.
+ */
+ public static final int PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX = 2;
+ @IntDef(flag = true, prefix = "PER_UID_MODEM_MODEL_", value = {
+ PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME,
+ PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PerUidModemPowerModel {
+ }
+
+ /**
* Use a queue to delay removing UIDs from {@link KernelCpuUidUserSysTimeReader},
* {@link KernelCpuUidActiveTimeReader}, {@link KernelCpuUidClusterTimeReader},
* {@link KernelCpuUidFreqTimeReader} and from the Kernel.
@@ -12038,8 +12056,7 @@
}
final SparseDoubleArray uidEstimatedConsumptionMah;
- if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null
- && mGlobalEnergyConsumerStats != null) {
+ if (consumedChargeUC > 0 && isMobileRadioEnergyConsumerSupportedLocked()) {
mGlobalEnergyConsumerStats.updateStandardBucket(
EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC);
uidEstimatedConsumptionMah = new SparseDoubleArray();
@@ -12047,6 +12064,8 @@
uidEstimatedConsumptionMah = null;
}
+ RxTxConsumption rxTxConsumption = null;
+ boolean attributeWithModemActivityInfo = false;
if (deltaInfo != null) {
mHasModemReporting = true;
mModemActivity.getOrCreateIdleTimeCounter()
@@ -12092,7 +12111,11 @@
mTmpRailStats.resetCellularTotalEnergyUsed();
}
- incrementPerRatDataLocked(deltaInfo, elapsedRealtimeMs);
+ rxTxConsumption = incrementPerRatDataLocked(deltaInfo, elapsedRealtimeMs);
+
+ attributeWithModemActivityInfo = mConstants.PER_UID_MODEM_MODEL
+ == PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX
+ && rxTxConsumption != null;
}
long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000);
@@ -12156,12 +12179,23 @@
(totalAppRadioTimeUs * appPackets) / totalPackets;
u.noteMobileRadioActiveTimeLocked(appRadioTimeUs, elapsedRealtimeMs);
- // Distribute mobile radio charge consumption based on app radio
- // active time
if (uidEstimatedConsumptionMah != null) {
- uidEstimatedConsumptionMah.incrementValue(u.getUid(),
+ final double uidConsumptionMah;
+ if (attributeWithModemActivityInfo) {
+ // Distribute measured mobile radio charge consumption based on
+ // rx/tx packets and estimated rx/tx charge consumption.
+ uidConsumptionMah = smearModemActivityInfoRxTxConsumptionMah(
+ rxTxConsumption, entry.getRxPackets(), entry.getTxPackets(),
+ totalRxPackets, totalTxPackets);
+ } else {
+ // Distribute mobile radio charge consumption based on app radio
+ // active time
+ uidConsumptionMah =
mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
- appRadioTimeUs / 1000));
+ appRadioTimeUs / 1000);
+ }
+ uidEstimatedConsumptionMah.incrementValue(u.getUid(),
+ uidConsumptionMah);
}
// Remove this app from the totals, so that we don't lose any time
@@ -12199,54 +12233,95 @@
mMobileRadioActiveUnknownCount.addCountLocked(1);
}
-
// Update the EnergyConsumerStats information.
if (uidEstimatedConsumptionMah != null) {
double totalEstimatedConsumptionMah = 0.0;
-
- // Estimate total active radio power consumption since last mark.
- final long totalRadioTimeMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked(
- elapsedRealtimeMs * 1000) / 1000;
- mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
- totalEstimatedConsumptionMah +=
- mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
- totalRadioTimeMs);
-
- // Estimate idle power consumption at each signal strength level
- final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length;
- for (int strengthLevel = 0; strengthLevel < numSignalStrengthLevels;
- strengthLevel++) {
- final long strengthLevelDurationMs =
- mPhoneSignalStrengthsTimer[strengthLevel].getTimeSinceMarkLocked(
+ if (attributeWithModemActivityInfo) {
+ // Estimate inactive modem power consumption and combine with previously
+ // estimated active power consumption for an estimate of total modem
+ // power consumption.
+ final long sleepTimeMs = deltaInfo.getSleepTimeMillis();
+ final long idleTimeMs = deltaInfo.getIdleTimeMillis();
+ final double inactiveConsumptionMah =
+ mMobileRadioPowerCalculator.calcInactiveStatePowerMah(sleepTimeMs,
+ idleTimeMs);
+ totalEstimatedConsumptionMah += inactiveConsumptionMah;
+ totalEstimatedConsumptionMah += rxTxConsumption.rxConsumptionMah;
+ totalEstimatedConsumptionMah += rxTxConsumption.txConsumptionMah;
+ } else {
+ // Estimate total active radio power consumption since last mark.
+ final long totalRadioTimeMs =
+ mMobileRadioActiveTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
- mPhoneSignalStrengthsTimer[strengthLevel].setMark(elapsedRealtimeMs);
-
+ mMobileRadioActiveTimer.setMark(elapsedRealtimeMs);
totalEstimatedConsumptionMah +=
- mMobileRadioPowerCalculator.calcIdlePowerAtSignalStrengthMah(
- strengthLevelDurationMs, strengthLevel);
+ mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
+ totalRadioTimeMs);
+
+ // Estimate idle power consumption at each signal strength level
+ final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length;
+ for (int lvl = 0; lvl < numSignalStrengthLevels; lvl++) {
+ final long strengthLevelDurationMs =
+ mPhoneSignalStrengthsTimer[lvl].getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ mPhoneSignalStrengthsTimer[lvl].setMark(elapsedRealtimeMs);
+
+ totalEstimatedConsumptionMah +=
+ mMobileRadioPowerCalculator.calcIdlePowerAtSignalStrengthMah(
+ strengthLevelDurationMs, lvl);
+ }
+
+ // Estimate total active radio power consumption since last mark.
+ final long scanTimeMs = mPhoneSignalScanningTimer.getTimeSinceMarkLocked(
+ elapsedRealtimeMs * 1000) / 1000;
+ mPhoneSignalScanningTimer.setMark(elapsedRealtimeMs);
+ totalEstimatedConsumptionMah +=
+ mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs);
}
-
- // Estimate total active radio power consumption since last mark.
- final long scanTimeMs = mPhoneSignalScanningTimer.getTimeSinceMarkLocked(
- elapsedRealtimeMs * 1000) / 1000;
- mPhoneSignalScanningTimer.setMark(elapsedRealtimeMs);
- totalEstimatedConsumptionMah +=
- mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs);
-
distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO,
consumedChargeUC, uidEstimatedConsumptionMah,
totalEstimatedConsumptionMah, elapsedRealtimeMs);
}
+ }
+ }
+ }
- delta = null;
+ private static class RxTxConsumption {
+ public final double rxConsumptionMah;
+ public final long rxDurationMs;
+ public final double txConsumptionMah;
+ public final long txDurationMs;
+
+ /**
+ * Represents the ratio between time spent transmitting and the total active time.
+ */
+ public final double txToTotalRatio;
+
+ RxTxConsumption(double rxMah, long rxMs, double txMah, long txMs) {
+ rxConsumptionMah = rxMah;
+ rxDurationMs = rxMs;
+ txConsumptionMah = txMah;
+ txDurationMs = txMs;
+
+ final long activeDurationMs = txDurationMs + rxDurationMs;
+ if (activeDurationMs == 0) {
+ txToTotalRatio = 0.0;
+ } else {
+ txToTotalRatio = ((double) txDurationMs) / activeDurationMs;
}
}
}
@GuardedBy("this")
- private void incrementPerRatDataLocked(ModemActivityInfo deltaInfo, long elapsedRealtimeMs) {
- final int infoSize = deltaInfo.getSpecificInfoLength();
+ @Nullable
+ private RxTxConsumption incrementPerRatDataLocked(ModemActivityInfo deltaInfo,
+ long elapsedRealtimeMs) {
+ double rxConsumptionMah = 0.0;
+ long rxDurationMs = 0;
+ double txConsumptionMah = 0.0;
+ long txDurationMs = 0;
+ final int infoSize = deltaInfo.getSpecificInfoLength();
if (infoSize == 1 && deltaInfo.getSpecificInfoRat(0)
== AccessNetworkConstants.AccessNetworkType.UNKNOWN
&& deltaInfo.getSpecificInfoFrequencyRange(0)
@@ -12296,6 +12371,16 @@
+ (totalLvlDurationMs / 2)) / totalLvlDurationMs;
ratStats.incrementTxDuration(freq, level, proportionalTxDurationMs);
frequencyDurationMs += durationMs;
+
+ if (isMobileRadioEnergyConsumerSupportedLocked()) {
+ // Accumulate the power cost of time spent transmitting in a
+ // particular state.
+ final double txStatePowerConsumptionMah =
+ mMobileRadioPowerCalculator.calcTxStatePowerMah(rat, freq,
+ level, proportionalTxDurationMs);
+ txConsumptionMah += txStatePowerConsumptionMah;
+ txDurationMs += proportionalTxDurationMs;
+ }
}
final long totalRxDuration = deltaInfo.getReceiveTimeMillis();
// Smear HAL provided Rx power duration based on active modem
@@ -12305,6 +12390,16 @@
(frequencyDurationMs * totalRxDuration + (totalActiveTimeMs
/ 2)) / totalActiveTimeMs;
ratStats.incrementRxDuration(freq, proportionalRxDurationMs);
+
+ if (isMobileRadioEnergyConsumerSupportedLocked()) {
+ // Accumulate the power cost of time spent receiving in a particular
+ // state.
+ final double rxStatePowerConsumptionMah =
+ mMobileRadioPowerCalculator.calcRxStatePowerMah(rat, freq,
+ proportionalRxDurationMs);
+ rxConsumptionMah += rxStatePowerConsumptionMah;
+ rxDurationMs += proportionalRxDurationMs;
+ }
}
}
@@ -12324,9 +12419,28 @@
final int[] txTimesMs = deltaInfo.getTransmitTimeMillis(rat, freq);
ratStats.incrementRxDuration(freq, rxTimeMs);
+ if (isMobileRadioEnergyConsumerSupportedLocked()) {
+ // Accumulate the power cost of time spent receiving in a particular state.
+ final double rxStatePowerConsumptionMah =
+ mMobileRadioPowerCalculator.calcRxStatePowerMah(ratBucket, freq,
+ rxTimeMs);
+ rxConsumptionMah += rxStatePowerConsumptionMah;
+ rxDurationMs += rxTimeMs;
+ }
+
final int numTxLvl = txTimesMs.length;
for (int lvl = 0; lvl < numTxLvl; lvl++) {
- ratStats.incrementTxDuration(freq, lvl, txTimesMs[lvl]);
+ final long txTimeMs = txTimesMs[lvl];
+ ratStats.incrementTxDuration(freq, lvl, txTimeMs);
+ if (isMobileRadioEnergyConsumerSupportedLocked()) {
+ // Accumulate the power cost of time spent transmitting in a particular
+ // state.
+ final double txStatePowerConsumptionMah =
+ mMobileRadioPowerCalculator.calcTxStatePowerMah(ratBucket, freq,
+ lvl, txTimeMs);
+ txConsumptionMah += txStatePowerConsumptionMah;
+ txDurationMs += txTimeMs;
+ }
}
}
}
@@ -12336,6 +12450,45 @@
if (ratStats == null) continue;
ratStats.setMark(elapsedRealtimeMs);
}
+
+ if (isMobileRadioEnergyConsumerSupportedLocked()) {
+ return new RxTxConsumption(rxConsumptionMah, rxDurationMs, txConsumptionMah,
+ txDurationMs);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Smear modem Rx/Tx power consumption calculated from {@link ModemActivityInfo} using Rx/Tx
+ * packets.
+ *
+ * @return the combine Rx/Tx smeared power consumption in milliamp-hours.
+ */
+ private double smearModemActivityInfoRxTxConsumptionMah(RxTxConsumption rxTxConsumption,
+ long rxPackets, long txPackets, long totalRxPackets, long totalTxPackets) {
+ // Distribute measured mobile radio charge consumption based on
+ // rx/tx packets and estimated rx/tx charge consumption.
+ double consumptionMah = 0.0;
+ if (totalRxPackets != 0) {
+ // Proportionally distribute receive battery consumption.
+ consumptionMah += rxTxConsumption.rxConsumptionMah * rxPackets
+ / totalRxPackets;
+ }
+ if (totalTxPackets != 0 || (totalRxPackets != 0 && rxTxConsumption.txToTotalRatio != 0.0)) {
+ // ModemActivityInfo Tx time represents time spent both transmitting and receiving.
+ // There is currently no way to distinguish how many Rx packets were received during
+ // Rx time vs Tx time.
+ // Assumption: The number of packets received while transmitting is proportional
+ // to the time spent transmitting over total active time.
+ final double totalPacketsDuringTxTime =
+ totalTxPackets + rxTxConsumption.txToTotalRatio * totalRxPackets;
+ final double packetsDuringTxTime =
+ txPackets + rxTxConsumption.txToTotalRatio * rxPackets;
+ consumptionMah += rxTxConsumption.txConsumptionMah * packetsDuringTxTime
+ / totalPacketsDuringTxTime;
+ }
+ return consumptionMah;
}
/**
@@ -14844,6 +14997,13 @@
}
}
+ @GuardedBy("this")
+ private boolean isMobileRadioEnergyConsumerSupportedLocked() {
+ if (mGlobalEnergyConsumerStats == null) return false;
+ return mGlobalEnergyConsumerStats.isStandardBucketSupported(
+ EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO);
+ }
+
@NonNull
private static String[] getBatteryConsumerProcessStateNames() {
String[] procStateNames = new String[BatteryConsumer.PROCESS_STATE_COUNT];
@@ -14879,6 +15039,40 @@
"battery_charged_delay_ms";
public static final String KEY_RECORD_USAGE_HISTORY =
"record_usage_history";
+ public static final String KEY_PER_UID_MODEM_POWER_MODEL =
+ "per_uid_modem_power_model";
+
+ public static final String PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME_NAME =
+ "mobile_radio_active_time";
+ public static final String PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX_NAME =
+ "modem_activity_info_rx_tx";
+
+ /** Convert {@link PerUidModemPowerModel} to string */
+ public String getPerUidModemModelName(@PerUidModemPowerModel int model) {
+ switch(model) {
+ case PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME:
+ return PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME_NAME;
+ case PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX:
+ return PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX_NAME;
+ default:
+ Slog.w(TAG, "Unexpected per uid modem model (" + model + ")");
+ return "unknown_" + model;
+ }
+ }
+
+ /** Convert string to {@link PerUidModemPowerModel} */
+ @PerUidModemPowerModel
+ public int getPerUidModemModel(String name) {
+ switch(name) {
+ case PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME_NAME:
+ return PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME;
+ case PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX_NAME:
+ return PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX;
+ default:
+ Slog.w(TAG, "Unexpected per uid modem model name (" + name + ")");
+ return DEFAULT_PER_UID_MODEM_MODEL;
+ }
+ }
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
@@ -14892,6 +15086,9 @@
private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
private static final boolean DEFAULT_RECORD_USAGE_HISTORY = false;
+ @PerUidModemPowerModel
+ private static final int DEFAULT_PER_UID_MODEM_MODEL =
+ PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
/* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
@@ -14908,6 +15105,7 @@
public int MAX_HISTORY_BUFFER; /*Bytes*/
public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
public boolean RECORD_USAGE_HISTORY = DEFAULT_RECORD_USAGE_HISTORY;
+ public int PER_UID_MODEM_MODEL = DEFAULT_PER_UID_MODEM_MODEL;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -14985,6 +15183,9 @@
* 1024;
RECORD_USAGE_HISTORY = mParser.getBoolean(
KEY_RECORD_USAGE_HISTORY, DEFAULT_RECORD_USAGE_HISTORY);
+ final String perUidModemModel = mParser.getString(KEY_PER_UID_MODEM_POWER_MODEL,
+ "");
+ PER_UID_MODEM_MODEL = getPerUidModemModel(perUidModemModel);
updateBatteryChargedDelayMsLocked();
@@ -15053,6 +15254,8 @@
pw.println(BATTERY_CHARGED_DELAY_MS);
pw.print(KEY_RECORD_USAGE_HISTORY); pw.print("=");
pw.println(RECORD_USAGE_HISTORY);
+ pw.print(KEY_PER_UID_MODEM_POWER_MODEL); pw.print("=");
+ pw.println(getPerUidModemModelName(PER_UID_MODEM_MODEL));
}
}
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 4608e9a..3226260 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -270,25 +270,28 @@
// Calculate the inactive modem power consumption.
final BatteryStats.ControllerActivityCounter modemActivity =
batteryStats.getModemControllerActivity();
- if (modemActivity != null && (mSleepPowerEstimator != null
- || mIdlePowerEstimator != null)) {
+ double inactivePowerMah = Double.NaN;
+ if (modemActivity != null) {
final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
BatteryStats.STATS_SINCE_CHARGED);
- total.remainingPowerMah += mSleepPowerEstimator.calculatePower(sleepDurationMs);
final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
BatteryStats.STATS_SINCE_CHARGED);
- total.remainingPowerMah += mIdlePowerEstimator.calculatePower(idleDurationMs);
- } else {
+ inactivePowerMah = calcInactiveStatePowerMah(sleepDurationMs, idleDurationMs);
+ }
+ if (Double.isNaN(inactivePowerMah)) {
// Modem activity counters unavailable. Use legacy calculations for inactive usage.
final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
- total.remainingPowerMah += calcScanTimePowerMah(scanningTimeMs);
+ inactivePowerMah = calcScanTimePowerMah(scanningTimeMs);
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
- total.remainingPowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
+ inactivePowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
}
}
+ if (!Double.isNaN(inactivePowerMah)) {
+ total.remainingPowerMah += inactivePowerMah;
+ }
}
@@ -509,6 +512,22 @@
}
/**
+ * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
+ * duration.
+ */
+ public double calcInactiveStatePowerMah(long sleepDurationMs, long idleDurationMs) {
+ if (mSleepPowerEstimator == null || mIdlePowerEstimator == null) return Double.NaN;
+ final double sleepConsumptionMah = mSleepPowerEstimator.calculatePower(sleepDurationMs);
+ final double idleConsumptionMah = mIdlePowerEstimator.calculatePower(idleDurationMs);
+ if (DEBUG) {
+ Log.d(TAG, "Calculated sleep consumption " + sleepConsumptionMah
+ + " mAH from a duration of " + sleepDurationMs + " ms and idle consumption "
+ + idleConsumptionMah + " mAH from a duration of " + idleDurationMs);
+ }
+ return sleepConsumptionMah + idleConsumptionMah;
+ }
+
+ /**
* Calculates active radio power consumption (in milliamp-hours) from active radio duration.
*/
public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
new file mode 100644
index 0000000..a380dea
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 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.wallpaper;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.Debug;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.function.Consumer;
+/**
+ * Internal class used to store all the display data relevant to the wallpapers
+ */
+class WallpaperDisplayHelper {
+
+ @VisibleForTesting
+ static final class DisplayData {
+ int mWidth = -1;
+ int mHeight = -1;
+ final Rect mPadding = new Rect(0, 0, 0, 0);
+ final int mDisplayId;
+ DisplayData(int displayId) {
+ mDisplayId = displayId;
+ }
+ }
+
+ private static final String TAG = WallpaperDisplayHelper.class.getSimpleName();
+ private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
+ private final DisplayManager mDisplayManager;
+ private final WindowManagerInternal mWindowManagerInternal;
+
+ WallpaperDisplayHelper(
+ DisplayManager displayManager,
+ WindowManagerInternal windowManagerInternal) {
+ mDisplayManager = displayManager;
+ mWindowManagerInternal = windowManagerInternal;
+ }
+
+ DisplayData getDisplayDataOrCreate(int displayId) {
+ DisplayData wpdData = mDisplayDatas.get(displayId);
+ if (wpdData == null) {
+ wpdData = new DisplayData(displayId);
+ ensureSaneWallpaperDisplaySize(wpdData, displayId);
+ mDisplayDatas.append(displayId, wpdData);
+ }
+ return wpdData;
+ }
+
+ void removeDisplayData(int displayId) {
+ mDisplayDatas.remove(displayId);
+ }
+
+ void ensureSaneWallpaperDisplaySize(DisplayData wpdData, int displayId) {
+ // We always want to have some reasonable width hint.
+ final int baseSize = getMaximumSizeDimension(displayId);
+ if (wpdData.mWidth < baseSize) {
+ wpdData.mWidth = baseSize;
+ }
+ if (wpdData.mHeight < baseSize) {
+ wpdData.mHeight = baseSize;
+ }
+ }
+
+ int getMaximumSizeDimension(int displayId) {
+ Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ Slog.w(TAG, "Invalid displayId=" + displayId + " " + Debug.getCallers(4));
+ display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+ }
+ return display.getMaximumSizeDimension();
+ }
+
+ void forEachDisplayData(Consumer<DisplayData> action) {
+ for (int i = mDisplayDatas.size() - 1; i >= 0; i--) {
+ final DisplayData wpdData = mDisplayDatas.valueAt(i);
+ action.accept(wpdData);
+ }
+ }
+
+ Display[] getDisplays() {
+ return mDisplayManager.getDisplays();
+ }
+
+ DisplayInfo getDisplayInfo(int displayId) {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
+ return displayInfo;
+ }
+
+ boolean isUsableDisplay(int displayId, int clientUid) {
+ return isUsableDisplay(mDisplayManager.getDisplay(displayId), clientUid);
+ }
+
+ boolean isUsableDisplay(Display display, int clientUid) {
+ if (display == null || !display.hasAccess(clientUid)) {
+ return false;
+ }
+ final int displayId = display.getDisplayId();
+ if (displayId == DEFAULT_DISPLAY) {
+ return true;
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ boolean isValidDisplay(int displayId) {
+ return mDisplayManager.getDisplay(displayId) != null;
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6edfebf..075bac1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -32,6 +32,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wallpaper.WallpaperDisplayHelper.DisplayData;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
@@ -79,7 +80,6 @@
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Debug;
import android.os.FileObserver;
import android.os.FileUtils;
import android.os.Handler;
@@ -609,10 +609,9 @@
boolean success = false;
// Only generate crop for default display.
- final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
final Rect cropHint = new Rect(wallpaper.cropHint);
- final DisplayInfo displayInfo = new DisplayInfo();
- mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
+ final DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(DEFAULT_DISPLAY);
if (DEBUG) {
Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
@@ -829,7 +828,8 @@
private final MyPackageMonitor mMonitor;
private final AppOpsManager mAppOpsManager;
- private final DisplayManager mDisplayManager;
+ // TODO("b/264637309") probably move this in WallpaperDisplayUtils,
+ // after logic is changed for the lockscreen lwp project
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@@ -848,12 +848,12 @@
targetWallpaper = mFallbackWallpaper;
}
if (targetWallpaper == null) return;
- WallpaperConnection.DisplayConnector connector =
+ DisplayConnector connector =
targetWallpaper.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
connector.disconnectLocked();
targetWallpaper.connection.removeDisplayConnector(displayId);
- removeDisplayData(displayId);
+ mWallpaperDisplayHelper.removeDisplayData(displayId);
}
for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) {
final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks =
@@ -904,8 +904,6 @@
private final SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
private final SparseArray<WallpaperData> mLockWallpaperMap = new SparseArray<WallpaperData>();
- private SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
-
protected WallpaperData mFallbackWallpaper;
private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
@@ -914,57 +912,7 @@
private LocalColorRepository mLocalColorRepo = new LocalColorRepository();
@VisibleForTesting
- static final class DisplayData {
- int mWidth = -1;
- int mHeight = -1;
- final Rect mPadding = new Rect(0, 0, 0, 0);
- final int mDisplayId;
-
- DisplayData(int displayId) {
- mDisplayId = displayId;
- }
- }
-
- private void removeDisplayData(int displayId) {
- mDisplayDatas.remove(displayId);
- }
-
- private DisplayData getDisplayDataOrCreate(int displayId) {
- DisplayData wpdData = mDisplayDatas.get(displayId);
- if (wpdData == null) {
- wpdData = new DisplayData(displayId);
- ensureSaneWallpaperDisplaySize(wpdData, displayId);
- mDisplayDatas.append(displayId, wpdData);
- }
- return wpdData;
- }
-
- private void ensureSaneWallpaperDisplaySize(DisplayData wpdData, int displayId) {
- // We always want to have some reasonable width hint.
- final int baseSize = getMaximumSizeDimension(displayId);
- if (wpdData.mWidth < baseSize) {
- wpdData.mWidth = baseSize;
- }
- if (wpdData.mHeight < baseSize) {
- wpdData.mHeight = baseSize;
- }
- }
-
- private int getMaximumSizeDimension(int displayId) {
- Display display = mDisplayManager.getDisplay(displayId);
- if (display == null) {
- Slog.w(TAG, "Invalid displayId=" + displayId + " " + Debug.getCallers(4));
- display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- }
- return display.getMaximumSizeDimension();
- }
-
- void forEachDisplayData(Consumer<DisplayData> action) {
- for (int i = mDisplayDatas.size() - 1; i >= 0; i--) {
- final DisplayData wpdData = mDisplayDatas.valueAt(i);
- action.accept(wpdData);
- }
- }
+ final WallpaperDisplayHelper mWallpaperDisplayHelper;
private boolean supportsMultiDisplay(WallpaperConnection connection) {
if (connection != null) {
@@ -993,7 +941,7 @@
}
} else {
fallbackConnection.appendConnectorWithCondition(display ->
- fallbackConnection.isUsableDisplay(display)
+ mWallpaperDisplayHelper.isUsableDisplay(display, fallbackConnection.mClientUid)
&& display.getDisplayId() != DEFAULT_DISPLAY
&& !fallbackConnection.containsDisplay(display.getDisplayId()));
fallbackConnection.forEachDisplayConnector(connector -> {
@@ -1004,84 +952,87 @@
}
}
- class WallpaperConnection extends IWallpaperConnection.Stub
- implements ServiceConnection {
+ /**
+ * Collect needed info for a display.
+ */
+ @VisibleForTesting
+ final class DisplayConnector {
+ final int mDisplayId;
+ final Binder mToken = new Binder();
+ IWallpaperEngine mEngine;
+ boolean mDimensionsChanged;
+ boolean mPaddingChanged;
- /**
- * Collect needed info for a display.
- */
- @VisibleForTesting
- final class DisplayConnector {
- final int mDisplayId;
- final Binder mToken = new Binder();
- IWallpaperEngine mEngine;
- boolean mDimensionsChanged;
- boolean mPaddingChanged;
+ DisplayConnector(int displayId) {
+ mDisplayId = displayId;
+ }
- DisplayConnector(int displayId) {
- mDisplayId = displayId;
- }
-
- void ensureStatusHandled() {
- final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
- if (mDimensionsChanged) {
- try {
- mEngine.setDesiredSize(wpdData.mWidth, wpdData.mHeight);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to set wallpaper dimensions", e);
- }
- mDimensionsChanged = false;
- }
- if (mPaddingChanged) {
- try {
- mEngine.setDisplayPadding(wpdData.mPadding);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to set wallpaper padding", e);
- }
- mPaddingChanged = false;
- }
- }
-
- void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
- if (connection.mService == null) {
- Slog.w(TAG, "WallpaperService is not connected yet");
- return;
- }
- TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
- t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
- if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
- mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
- null /* options */);
- final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
+ void ensureStatusHandled() {
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
+ if (mDimensionsChanged) {
try {
- connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
- wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId, mWallpaper.mWhich);
+ mEngine.setDesiredSize(wpdData.mWidth, wpdData.mHeight);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed attaching wallpaper on display", e);
- if (wallpaper != null && !wallpaper.wallpaperUpdating
- && connection.getConnectedEngineSize() == 0) {
- bindWallpaperComponentLocked(null /* componentName */, false /* force */,
- false /* fromUser */, wallpaper, null /* reply */);
- }
+ Slog.w(TAG, "Failed to set wallpaper dimensions", e);
}
- t.traceEnd();
+ mDimensionsChanged = false;
}
-
- void disconnectLocked() {
- if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken);
- mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */,
- mDisplayId);
+ if (mPaddingChanged) {
try {
- if (mEngine != null) {
- mEngine.destroy();
- }
+ mEngine.setDisplayPadding(wpdData.mPadding);
} catch (RemoteException e) {
+ Slog.w(TAG, "Failed to set wallpaper padding", e);
}
- mEngine = null;
+ mPaddingChanged = false;
}
}
+ void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
+ if (connection.mService == null) {
+ Slog.w(TAG, "WallpaperService is not connected yet");
+ return;
+ }
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
+ if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
+ mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
+ null /* options */);
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
+ try {
+ connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+ wpdData.mWidth, wpdData.mHeight,
+ wpdData.mPadding, mDisplayId, wallpaper.mWhich);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed attaching wallpaper on display", e);
+ if (wallpaper != null && !wallpaper.wallpaperUpdating
+ && connection.getConnectedEngineSize() == 0) {
+ bindWallpaperComponentLocked(null /* componentName */, false /* force */,
+ false /* fromUser */, wallpaper, null /* reply */);
+ }
+ }
+ t.traceEnd();
+ }
+
+ void disconnectLocked() {
+ if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken);
+ mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */,
+ mDisplayId);
+ try {
+ if (mEngine != null) {
+ mEngine.destroy();
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Engine.destroy() threw a RemoteException");
+ }
+ mEngine = null;
+ }
+ }
+
+ class WallpaperConnection extends IWallpaperConnection.Stub
+ implements ServiceConnection {
+
/**
* A map for each display.
* Use {@link #getDisplayConnectorOrCreate(int displayId)} to ensure the display is usable.
@@ -1132,7 +1083,8 @@
if (!mWallpaper.equals(mFallbackWallpaper)) {
if (supportsMultiDisplay(this)) {
// The system wallpaper is image wallpaper or it can supports multiple displays.
- appendConnectorWithCondition(this::isUsableDisplay);
+ appendConnectorWithCondition(display ->
+ mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid));
} else {
// The system wallpaper does not support multiple displays, so just attach it on
// default display.
@@ -1143,37 +1095,18 @@
}
private void appendConnectorWithCondition(Predicate<Display> tester) {
- final Display[] displays = mDisplayManager.getDisplays();
+ final Display[] displays = mWallpaperDisplayHelper.getDisplays();
for (Display display : displays) {
if (tester.test(display)) {
final int displayId = display.getDisplayId();
final DisplayConnector connector = mDisplayConnector.get(displayId);
if (connector == null) {
- mDisplayConnector.append(displayId,
- new DisplayConnector(displayId));
+ mDisplayConnector.append(displayId, new DisplayConnector(displayId));
}
}
}
}
- @VisibleForTesting
- boolean isUsableDisplay(Display display) {
- if (display == null || !display.hasAccess(mClientUid)) {
- return false;
- }
- final int displayId = display.getDisplayId();
- if (displayId == DEFAULT_DISPLAY) {
- return true;
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- return mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
void forEachDisplayConnector(Consumer<DisplayConnector> action) {
for (int i = mDisplayConnector.size() - 1; i >= 0; i--) {
final DisplayConnector connector = mDisplayConnector.valueAt(i);
@@ -1193,8 +1126,7 @@
DisplayConnector getDisplayConnectorOrCreate(int displayId) {
DisplayConnector connector = mDisplayConnector.get(displayId);
if (connector == null) {
- final Display display = mDisplayManager.getDisplay(displayId);
- if (isUsableDisplay(display)) {
+ if (mWallpaperDisplayHelper.isUsableDisplay(displayId, mClientUid)) {
connector = new DisplayConnector(displayId);
mDisplayConnector.append(displayId, connector);
}
@@ -1633,8 +1565,9 @@
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mIPackageManager = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
- mDisplayManager.registerDisplayListener(mDisplayListener, null /* handler */);
+ DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+ dm.registerDisplayListener(mDisplayListener, null /* handler */);
+ mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
mActivityManager = mContext.getSystemService(ActivityManager.class);
mMonitor = new MyPackageMonitor();
mColorsChangedListeners = new SparseArray<>();
@@ -2084,10 +2017,6 @@
return false;
}
- private boolean isValidDisplay(int displayId) {
- return mDisplayManager.getDisplay(displayId) != null;
- }
-
/**
* Sets the dimension hint for the wallpaper. These hints indicate the desired
* minimum width and height for the wallpaper in a particular display.
@@ -2110,18 +2039,18 @@
throw new IllegalArgumentException("width and height must be > 0");
}
- if (!isValidDisplay(displayId)) {
+ if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
throw new IllegalArgumentException("Cannot find display with id=" + displayId);
}
- final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
if (width != wpdData.mWidth || height != wpdData.mHeight) {
wpdData.mWidth = width;
wpdData.mHeight = height;
if (displayId == DEFAULT_DISPLAY) saveSettingsLocked(userId);
if (mCurrentUserId != userId) return; // Don't change the properties now
if (wallpaper.connection != null) {
- final WallpaperConnection.DisplayConnector connector = wallpaper.connection
+ final DisplayConnector connector = wallpaper.connection
.getDisplayConnectorOrCreate(displayId);
final IWallpaperEngine engine = connector != null ? connector.mEngine : null;
if (engine != null) {
@@ -2146,12 +2075,13 @@
*/
public int getWidthHint(int displayId) throws RemoteException {
synchronized (mLock) {
- if (!isValidDisplay(displayId)) {
+ if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
throw new IllegalArgumentException("Cannot find display with id=" + displayId);
}
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
if (wallpaper != null) {
- final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
return wpdData.mWidth;
} else {
return 0;
@@ -2164,12 +2094,13 @@
*/
public int getHeightHint(int displayId) throws RemoteException {
synchronized (mLock) {
- if (!isValidDisplay(displayId)) {
+ if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
throw new IllegalArgumentException("Cannot find display with id=" + displayId);
}
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
if (wallpaper != null) {
- final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
return wpdData.mHeight;
} else {
return 0;
@@ -2186,7 +2117,7 @@
return;
}
synchronized (mLock) {
- if (!isValidDisplay(displayId)) {
+ if (!mWallpaperDisplayHelper.isValidDisplay(displayId)) {
throw new IllegalArgumentException("Cannot find display with id=" + displayId);
}
int userId = UserHandle.getCallingUserId();
@@ -2195,7 +2126,7 @@
throw new IllegalArgumentException("padding must be positive: " + padding);
}
- int maxSize = getMaximumSizeDimension(displayId);
+ int maxSize = mWallpaperDisplayHelper.getMaximumSizeDimension(displayId);
final int paddingWidth = padding.left + padding.right;
final int paddingHeight = padding.top + padding.bottom;
@@ -2208,13 +2139,13 @@
+ " exceeds max height " + maxSize);
}
- final DisplayData wpdData = getDisplayDataOrCreate(displayId);
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(displayId);
if (!padding.equals(wpdData.mPadding)) {
wpdData.mPadding.set(padding);
if (displayId == DEFAULT_DISPLAY) saveSettingsLocked(userId);
if (mCurrentUserId != userId) return; // Don't change the properties now
if (wallpaper.connection != null) {
- final WallpaperConnection.DisplayConnector connector = wallpaper.connection
+ final DisplayConnector connector = wallpaper.connection
.getDisplayConnectorOrCreate(displayId);
final IWallpaperEngine engine = connector != null ? connector.mEngine : null;
if (engine != null) {
@@ -2268,7 +2199,8 @@
return null;
}
// Only for default display.
- final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpdData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
try {
if (outParams != null) {
outParams.putInt("width", wpdData.mWidth);
@@ -3155,8 +3087,7 @@
Slog.w(TAG, "Failed detaching wallpaper service ", e);
}
mContext.unbindService(wallpaper.connection);
- wallpaper.connection.forEachDisplayConnector(
- WallpaperConnection.DisplayConnector::disconnectLocked);
+ wallpaper.connection.forEachDisplayConnector(DisplayConnector::disconnectLocked);
wallpaper.connection.mService = null;
wallpaper.connection.mDisplayConnector.clear();
@@ -3286,7 +3217,7 @@
return;
}
if (supportsMultiDisplay(mLastWallpaper.connection)) {
- final WallpaperConnection.DisplayConnector connector =
+ final DisplayConnector connector =
mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
connector.connectLocked(mLastWallpaper.connection, mLastWallpaper);
@@ -3295,7 +3226,7 @@
// System wallpaper does not support multiple displays, attach this display to
// the fallback wallpaper.
if (mFallbackWallpaper != null) {
- final WallpaperConnection.DisplayConnector connector = mFallbackWallpaper
+ final DisplayConnector connector = mFallbackWallpaper
.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
connector.connectLocked(mFallbackWallpaper.connection, mFallbackWallpaper);
@@ -3352,7 +3283,7 @@
if (DEBUG) {
Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
}
- final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
out.startTag(null, tag);
out.attributeInt(null, "id", wallpaper.wallpaperId);
out.attributeInt(null, "width", wpdData.mWidth);
@@ -3536,7 +3467,7 @@
initializeFallbackWallpaper();
}
boolean success = false;
- final DisplayData wpdData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
try {
stream = new FileInputStream(file);
TypedXmlPullParser parser = Xml.resolvePullParser(stream);
@@ -3613,7 +3544,7 @@
}
}
- ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
+ mWallpaperDisplayHelper.ensureSaneWallpaperDisplaySize(wpdData, DEFAULT_DISPLAY);
ensureSaneWallpaperData(wallpaper);
WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
if (lockWallpaper != null) {
@@ -3653,7 +3584,7 @@
wallpaper.wallpaperId = makeWallpaperIdLocked();
}
- final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
if (!keepDimensionHints) {
wpData.mWidth = parser.getAttributeInt(null, "width");
@@ -3866,7 +3797,7 @@
pw.print(" User "); pw.print(wallpaper.userId);
pw.print(": id="); pw.println(wallpaper.wallpaperId);
pw.println(" Display state:");
- forEachDisplayData(wpSize -> {
+ mWallpaperDisplayHelper.forEachDisplayData(wpSize -> {
pw.print(" displayId=");
pw.println(wpSize.mDisplayId);
pw.print(" mWidth=");
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index af135b7..9e258cb 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -20,6 +20,7 @@
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+import static android.view.ViewProtoEnums.DISPLAY_STATE_OFF;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
@@ -317,6 +318,11 @@
if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
mRecordedWindowContainer.asTask().isVisibleRequested());
+ } else {
+ int currentDisplayState =
+ mRecordedWindowContainer.asDisplayContent().getDisplay().getState();
+ mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+ currentDisplayState != DISPLAY_STATE_OFF);
}
// No need to clean up. In SurfaceFlinger, parents hold references to their children. The
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index eadb11e..0d6c8db 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6841,12 +6841,12 @@
public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
try {
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
mRemoteInsetsController.showInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
- ImeTracker.get().onFailed(statsToken,
+ ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
}
}
@@ -6855,12 +6855,12 @@
public void hideInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
try {
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
mRemoteInsetsController.hideInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver hideInsets", e);
- ImeTracker.get().onFailed(statsToken,
+ ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
}
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 85938e3..60e2e95 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -182,7 +182,8 @@
boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
mImeRequester = imeTarget;
// There was still a stats token, so that request presumably failed.
- ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ ImeTracker.forLogging().onFailed(
+ mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
mImeRequesterStatsToken = statsToken;
if (targetChanged) {
// target changed, check if new target can show IME.
@@ -197,12 +198,12 @@
ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
? mImeRequester : mImeRequester.getWindow().getName());
mShowImeRunner = () -> {
- ImeTracker.get().onProgress(mImeRequesterStatsToken,
+ ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
// Target should still be the same.
if (isReadyToShowIme()) {
- ImeTracker.get().onProgress(mImeRequesterStatsToken,
+ ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
ImeTracker.PHASE_WM_SHOW_IME_READY);
final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
@@ -219,7 +220,7 @@
? mImeRequester.getWindow().getName() : ""));
}
} else {
- ImeTracker.get().onFailed(mImeRequesterStatsToken,
+ ImeTracker.forLogging().onFailed(mImeRequesterStatsToken,
ImeTracker.PHASE_WM_SHOW_IME_READY);
}
// Clear token here so we don't report an error in abortShowImePostLayout().
@@ -258,7 +259,8 @@
mImeRequester = null;
mIsImeLayoutDrawn = false;
mShowImeRunner = null;
- ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ ImeTracker.forLogging().onCancelled(
+ mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
mImeRequesterStatsToken = null;
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 1df534f..874f942 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -707,7 +707,8 @@
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
- false /* disable */, 0 /* floatingImeBottomInsets */, null);
+ false /* disable */, 0 /* floatingImeBottomInsets */,
+ null /* loggingListener */, null /* jankContext */);
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 75ba214..9c43c1d 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1146,10 +1146,9 @@
final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
true /* traverseTopToBottom */);
- if (firstOpaqueActivityBeneath == null
- || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) {
+ if (firstOpaqueActivityBeneath == null) {
// We skip letterboxing if the translucent activity doesn't have any opaque
- // activities beneath of if it's launched from a different user (e.g. notification)
+ // activities beneath
return;
}
inheritConfiguration(firstOpaqueActivityBeneath);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e253ce0..b7021c8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -610,7 +610,7 @@
*/
ActivityRecord mChildPipActivity;
- boolean mLastSurfaceShowing = true;
+ boolean mLastSurfaceShowing;
boolean mAlignActivityLocaleWithTask = false;
@@ -4131,13 +4131,7 @@
@Override
boolean showSurfaceOnCreation() {
- if (mCreatedByOrganizer) {
- // Tasks created by the organizer are default visible because they can synchronously
- // update the leash before new children are added to the task.
- return true;
- }
- // Organized tasks handle their own surface visibility
- return !canBeOrganized();
+ return false;
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a3827c0..db17ae1 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1958,7 +1958,8 @@
}
boolean shouldSleepActivities() {
- return false;
+ final Task task = getRootTask();
+ return task != null && task.shouldSleepActivities();
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 5f186a1..a27cc3a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -541,9 +541,12 @@
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
- return organizerState != null
- ? organizerState.mRemoteAnimationDefinition
- : null;
+ if (organizerState == null) {
+ Slog.e(TAG, "TaskFragmentOrganizer has been unregistered or died when trying"
+ + " to play animation on its organized windows.");
+ return null;
+ }
+ return organizerState.mRemoteAnimationDefinition;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0243a98..5adae41 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8071,14 +8071,14 @@
dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
}
if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) {
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ",
dc.getImeTarget(IME_TARGET_CONTROL));
dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(),
true /* fromIme */, statsToken);
} else {
- ImeTracker.get().onFailed(statsToken,
+ ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
}
if (dc != null) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0a5e0b7..6a1adb4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1932,6 +1932,8 @@
ownerTask.addChild(taskFragment, position);
taskFragment.setWindowingMode(creationParams.getWindowingMode());
taskFragment.setBounds(creationParams.getInitialBounds());
+ // Record the initial relative embedded bounds.
+ taskFragment.updateRelativeEmbeddedBounds();
mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
if (transition != null) transition.collectExistenceChange(taskFragment);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e08bacc..7a0070b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -388,14 +388,6 @@
int mPrepareSyncSeqId = 0;
/**
- * {@code true} when the client was still drawing for sync when the sync-set was finished or
- * cancelled. This can happen if the window goes away during a sync. In this situation we need
- * to make sure to still apply the postDrawTransaction when it finishes to prevent the client
- * from getting stuck in a bad state.
- */
- boolean mClientWasDrawingForSync = false;
-
- /**
* Special mode that is intended only for the rounded corner overlay: during rotation
* transition, we un-rotate the window token such that the window appears as it did before the
* rotation.
@@ -4001,12 +3993,12 @@
public void showInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
try {
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
mClient.showInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
- ImeTracker.get().onFailed(statsToken,
+ ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
}
}
@@ -4015,12 +4007,12 @@
public void hideInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
try {
- ImeTracker.get().onProgress(statsToken,
+ ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
mClient.hideInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver hideInsets", e);
- ImeTracker.get().onFailed(statsToken,
+ ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
}
}
@@ -5977,9 +5969,6 @@
@Override
void finishSync(Transaction outMergedTransaction, boolean cancel) {
- if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
- mClientWasDrawingForSync = true;
- }
mPrepareSyncSeqId = 0;
if (cancel) {
// This is leaving sync so any buffers left in the sync have a chance of
@@ -6047,9 +6036,7 @@
layoutNeeded = onSyncFinishedDrawing();
}
- layoutNeeded |=
- mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync);
- mClientWasDrawingForSync = false;
+ layoutNeeded |= mWinAnimator.finishDrawingLocked(postDrawTransaction);
// We always want to force a traversal after a finish draw for blast sync.
return !skipLayout && (hasSyncHandlers || layoutNeeded);
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a1f4096..a102986 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -151,17 +151,6 @@
int mAttrType;
- /**
- * Handles surface changes synchronized to after the client has drawn the surface. This
- * transaction is currently used to reparent the old surface children to the new surface once
- * the client has completed drawing to the new surface.
- * This transaction is also used to merge transactions parceled in by the client. The client
- * uses the transaction to update the relative z of its children from the old parent surface
- * to the new parent surface once window manager reparents its children.
- */
- private final SurfaceControl.Transaction mPostDrawTransaction =
- new SurfaceControl.Transaction();
-
WindowStateAnimator(final WindowState win) {
final WindowManagerService service = win.mWmService;
@@ -217,8 +206,7 @@
}
}
- boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction,
- boolean forceApplyNow) {
+ boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
final boolean startingWindow =
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
if (startingWindow) {
@@ -240,14 +228,7 @@
}
if (postDrawTransaction != null) {
- // If there is no surface, the last draw was for the previous surface. We don't want to
- // wait until the new surface is shown and instead just apply the transaction right
- // away.
- if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) {
- mPostDrawTransaction.merge(postDrawTransaction);
- } else {
- mWin.getSyncTransaction().merge(postDrawTransaction);
- }
+ mWin.getSyncTransaction().merge(postDrawTransaction);
layoutNeeded = true;
}
@@ -543,7 +524,6 @@
if (!shown)
return false;
- t.merge(mPostDrawTransaction);
return true;
}
@@ -710,10 +690,6 @@
}
void destroySurface(SurfaceControl.Transaction t) {
- // Since the SurfaceControl is getting torn down, it's safe to just clean up any
- // pending transactions that were in mPostDrawTransaction, as well.
- t.merge(mPostDrawTransaction);
-
try {
if (mSurfaceController != null) {
mSurfaceController.destroy(t);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8be3df4..fa6fa53 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -146,6 +146,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
@@ -1091,13 +1092,13 @@
// (ACTION_DATE_CHANGED), or when manual clock adjustment is made
// (ACTION_TIME_CHANGED)
updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true);
- final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
+ final int userId = getManagedUserId(mUserManager.getMainUser().getIdentifier());
if (userId >= 0) {
updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
}
} else if (ACTION_PROFILE_OFF_DEADLINE.equals(action)) {
Slogf.i(LOG_TAG, "Profile off deadline alarm was triggered");
- final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
+ final int userId = getManagedUserId(mUserManager.getMainUser().getIdentifier());
if (userId >= 0) {
updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
} else {
@@ -8597,9 +8598,20 @@
Preconditions.checkArgument(admin != null);
final CallerIdentity caller = getCallerIdentity();
- // Cannot be called while holding the lock:
- final boolean hasIncompatibleAccountsOrNonAdb =
- hasIncompatibleAccountsOrNonAdbNoLock(caller, userId, admin);
+
+ boolean hasIncompatibleAccountsOrNonAdb =
+ !isAdb(caller) || hasIncompatibleAccountsOnAnyUser();
+
+ if (!hasIncompatibleAccountsOrNonAdb) {
+ synchronized (getLockObject()) {
+ if (!isAdminTestOnlyLocked(admin, userId) && hasAccountsOnAnyUser()) {
+ Slogf.w(LOG_TAG,
+ "Non test-only owner can't be installed with existing accounts.");
+ return false;
+ }
+ }
+ }
+
synchronized (getLockObject()) {
enforceCanSetDeviceOwnerLocked(caller, admin, userId, hasIncompatibleAccountsOrNonAdb);
Preconditions.checkArgument(isPackageInstalledForUser(admin.getPackageName(), userId),
@@ -14745,14 +14757,10 @@
if (isAdb) {
// If shell command runs after user setup completed check device status. Otherwise, OK.
if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
- // In non-headless system user mode, DO can be setup only if
- // there's no non-system user.
- // In headless system user mode, DO can be setup only if there are
- // two users: the headless system user and the foreground user.
- // If there could be multiple foreground users, this constraint should be modified.
+ // DO can be setup only if there are no users which are neither created by default
+ // nor marked as FOR_TESTING
- int maxNumberOfExistingUsers = isHeadlessSystemUserMode ? 2 : 1;
- if (mUserManager.getUserCount() > maxNumberOfExistingUsers) {
+ if (nonTestNonPrecreatedUsersExist()) {
return STATUS_NONSYSTEM_USER_EXISTS;
}
@@ -14782,6 +14790,17 @@
}
}
+ /**
+ * True if there are any users on the device which were not setup by default (1 usually, 2 for
+ * devices with a headless system user) and also are not marked as FOR_TESTING.
+ */
+ private boolean nonTestNonPrecreatedUsersExist() {
+ int allowedUsers = UserManager.isHeadlessSystemUserMode() ? 2 : 1;
+ return mUserManagerInternal.getUsers(/* excludeDying= */ true).stream()
+ .filter(u -> !u.isForTesting())
+ .count() > allowedUsers;
+ }
+
private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
synchronized (getLockObject()) {
final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
@@ -16053,8 +16072,10 @@
wtfIfInLock();
return mInjector.binderWithCleanCallingIdentity(() -> {
- final AccountManager am = AccountManager.get(mContext);
- final Account accounts[] = am.getAccountsAsUser(userId);
+ AccountManager am =
+ mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0)
+ .getSystemService(AccountManager.class);
+ Account[] accounts = am.getAccounts();
if (accounts.length == 0) {
return false;
}
@@ -18168,8 +18189,10 @@
.addAction(turnProfileOnButton)
.addExtras(extras)
.build();
- mInjector.getNotificationManager().notify(
- SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification);
+
+ mInjector.getNotificationManager().notifyAsUser(
+ null, SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification,
+ UserHandle.of(getProfileParentId(profileUserId)));
}
private String getPersonalAppSuspensionButtonText() {
@@ -19125,6 +19148,9 @@
if (preferentialNetworkServiceConfig.isEnabled()) {
if (preferentialNetworkServiceConfig.isFallbackToDefaultConnectionAllowed()) {
preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ } else if (preferentialNetworkServiceConfig.shouldBlockNonMatchingNetworks()) {
+ preferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING);
} else {
preferenceBuilder.setPreference(
PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
@@ -19533,6 +19559,14 @@
}
@Override
+ public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
+ Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+ setBypassDevicePolicyManagementRoleQualificationStateInternal(
+ /* currentRoleHolder= */ null, /* allowBypass= */ false);
+ }
+
+ @Override
public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification() {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ROLE_HOLDERS));
@@ -19546,15 +19580,51 @@
}
private boolean shouldAllowBypassingDevicePolicyManagementRoleQualificationInternal() {
- if (mUserManager.getUserCount() > 1) {
+ if (nonTestNonPrecreatedUsersExist()) {
return false;
}
- AccountManager am = AccountManager.get(mContext);
- Account[] accounts = am.getAccounts();
- if (accounts.length == 0) {
- return true;
+
+
+ return !hasIncompatibleAccountsOnAnyUser();
+ }
+
+ private boolean hasAccountsOnAnyUser() {
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ for (UserInfo user : mUserManagerInternal.getUsers(/* excludeDying= */ true)) {
+ AccountManager am = mContext.createContextAsUser(
+ UserHandle.of(user.id), /* flags= */ 0)
+ .getSystemService(AccountManager.class);
+ Account[] accounts = am.getAccounts();
+ if (accounts.length != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
}
- return !hasIncompatibleAccounts(am, accounts);
+ }
+
+ private boolean hasIncompatibleAccountsOnAnyUser() {
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ for (UserInfo user : mUserManagerInternal.getUsers(/* excludeDying= */ true)) {
+ AccountManager am = mContext.createContextAsUser(
+ UserHandle.of(user.id), /* flags= */ 0)
+ .getSystemService(AccountManager.class);
+ Account[] accounts = am.getAccounts();
+
+ if (hasIncompatibleAccounts(am, accounts)) {
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
}
private void setBypassDevicePolicyManagementRoleQualificationStateInternal(
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index deebfc7..3d71739 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -17,7 +17,6 @@
import android.annotation.UserIdInt;
import android.content.ContentResolver;
-import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -27,8 +26,6 @@
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
-import java.util.List;
-
/**
* Class responsible for booting the device in the proper user on headless system user mode.
*
@@ -56,50 +53,18 @@
// this class or the setup wizard app
provisionHeadlessSystemUser();
- UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
- t.traceBegin("get-existing-users");
- List<UserInfo> existingUsers = um.getUsers(/* excludeDying= */ true);
- t.traceEnd();
-
- Slogf.d(TAG, "%d existing users", existingUsers.size());
-
- int initialUserId = UserHandle.USER_NULL;
-
- for (int i = 0; i < existingUsers.size(); i++) {
- UserInfo user = existingUsers.get(i);
- if (DEBUG) {
- Slogf.d(TAG, "User at position %d: %s", i, user.toFullString());
- }
- if (user.id != UserHandle.USER_SYSTEM && user.isFull()) {
- if (DEBUG) {
- Slogf.d(TAG, "Found initial user: %d", user.id);
- }
- initialUserId = user.id;
- break;
- }
- }
-
- if (initialUserId == UserHandle.USER_NULL) {
- Slogf.d(TAG, "Creating initial user");
- t.traceBegin("create-initial-user");
- try {
- int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
- // TODO(b/204091126): proper name for user
- UserInfo newUser = um.createUserEvenWhenDisallowed("Real User",
- UserManager.USER_TYPE_FULL_SECONDARY, flags,
- /* disallowedPackages= */ null, /* token= */ null);
- Slogf.i(TAG, "Created initial user: %s", newUser.toFullString());
- initialUserId = newUser.id;
- } catch (Exception e) {
- Slogf.wtf(TAG, "failed to created initial user", e);
- return;
- } finally {
- t.traceEnd(); // create-initial-user
- }
- }
-
unlockSystemUser(t);
- switchToInitialUser(initialUserId);
+
+ try {
+ t.traceBegin("getBootUser");
+ int bootUser = LocalServices.getService(UserManagerInternal.class).getBootUser();
+ t.traceEnd();
+ t.traceBegin("switchToBootUser-" + bootUser);
+ switchToBootUser(bootUser);
+ t.traceEnd();
+ } catch (UserManager.CheckedUserOperationException e) {
+ Slogf.wtf(TAG, "Failed to created boot user", e);
+ }
}
/* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
@@ -152,12 +117,12 @@
}
}
- private void switchToInitialUser(@UserIdInt int initialUserId) {
- Slogf.i(TAG, "Switching to initial user %d", initialUserId);
- boolean started = mAms.startUserInForegroundWithListener(initialUserId,
+ private void switchToBootUser(@UserIdInt int bootUserId) {
+ Slogf.i(TAG, "Switching to boot user %d", bootUserId);
+ boolean started = mAms.startUserInForegroundWithListener(bootUserId,
/* unlockListener= */ null);
if (!started) {
- Slogf.wtf(TAG, "Failed to start user %d in foreground", initialUserId);
+ Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId);
}
}
}
diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
index 8a3a44ae..0993295 100644
--- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
@@ -21,6 +21,7 @@
import android.annotation.WorkerThread;
import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
@@ -149,6 +150,8 @@
found = true;
}
+ } catch (SQLiteException exception) {
+ Slog.w("SQLite exception when querying contacts.", exception);
}
if (found && lookupKey != null && hasPhoneNumber) {
return queryPhoneNumber(lookupKey);
diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
index c8797e2..dbc0da7 100644
--- a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
@@ -18,8 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
-
import android.annotation.UserIdInt;
import android.app.job.JobScheduler;
import android.content.Context;
@@ -33,8 +31,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
@@ -49,20 +45,14 @@
private BackupManagerConstants mConstants;
private ShadowJobScheduler mShadowJobScheduler;
- @Mock
- private UserBackupManagerService mUserBackupManagerService;
-
@UserIdInt private int mUserOneId;
@UserIdInt private int mUserTwoId;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
mConstants.start();
- when(mUserBackupManagerService.getConstants()).thenReturn(mConstants);
- when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class));
@@ -79,8 +69,8 @@
@Test
public void testSchedule_afterScheduling_jobExists() {
- FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
- FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
@@ -88,34 +78,18 @@
@Test
public void testCancel_afterCancelling_jobDoesntExist() {
- FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
- FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
FullBackupJob.cancel(mUserOneId, mContext);
FullBackupJob.cancel(mUserTwoId, mContext);
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
}
-
- @Test
- public void testSchedule_isNoopIfDisabled() {
- when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(false);
- FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
-
- assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
- }
-
- @Test
- public void testSchedule_schedulesJobIfEnabled() {
- when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
- FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
-
- assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
- }
//
@Test
public void testSchedule_onlySchedulesForRequestedUser() {
- FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
@@ -123,8 +97,8 @@
//
@Test
public void testCancel_onlyCancelsForRequestedUser() {
- FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
- FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
FullBackupJob.cancel(mUserOneId, mContext);
assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
diff --git a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
index 712ac55..1c5fac2 100644
--- a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
@@ -18,8 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
-
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.Handler;
@@ -32,8 +30,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -45,20 +41,14 @@
private Context mContext;
private BackupManagerConstants mConstants;
- @Mock
- private UserBackupManagerService mUserBackupManagerService;
-
@UserIdInt private int mUserOneId;
@UserIdInt private int mUserTwoId;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
mConstants.start();
- when(mUserBackupManagerService.getConstants()).thenReturn(mConstants);
- when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
mUserOneId = UserHandle.USER_SYSTEM;
mUserTwoId = mUserOneId + 1;
@@ -72,22 +62,6 @@
}
@Test
- public void testSchedule_isNoopIfDisabled() {
- when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(false);
- KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
-
- assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
- }
-
- @Test
- public void testSchedule_schedulesJobIfEnabled() {
- when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
- KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
-
- assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
- }
-
- @Test
public void testIsScheduled_beforeScheduling_returnsFalse() {
assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
@@ -95,8 +69,8 @@
@Test
public void testIsScheduled_afterScheduling_returnsTrue() {
- KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
- KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue();
@@ -104,8 +78,8 @@
@Test
public void testIsScheduled_afterCancelling_returnsFalse() {
- KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
- KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
KeyValueBackupJob.cancel(mUserOneId, mContext);
KeyValueBackupJob.cancel(mUserTwoId, mContext);
@@ -115,7 +89,7 @@
@Test
public void testIsScheduled_afterScheduling_returnsTrueOnlyForScheduledUser() {
- KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
@@ -123,8 +97,8 @@
@Test
public void testIsScheduled_afterCancelling_returnsFalseOnlyForCancelledUser() {
- KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
- KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
KeyValueBackupJob.cancel(mUserOneId, mContext);
assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index d969cd2..5f4ff1a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -93,6 +93,7 @@
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -448,9 +449,9 @@
UnaryOperator<Bundle> extrasOperator, ReceiverInfo info) {
final Intent intent = info.intent;
final Bundle extras = info.extras;
- final boolean ordered = info.ordered;
+ final boolean assumeDelivered = info.assumeDelivered;
mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
- if (!wedge && ordered) {
+ if (!wedge && !assumeDelivered) {
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
mQueue.getPreferredSchedulingGroupLocked(r));
@@ -701,6 +702,7 @@
ArgumentMatcher<String> data,
ArgumentMatcher<Bundle> extras,
Boolean sync,
+ Boolean assumeDelivered,
Integer sendingUser,
Integer processState) {
return (test) -> {
@@ -712,6 +714,7 @@
&& matchObject(data, test.data)
&& matchObject(extras, test.extras)
&& matchElement(sync, test.sync)
+ && matchElement(assumeDelivered, test.assumeDelivered)
&& matchElement(sendingUser, test.sendingUser)
&& matchElement(processState, test.processState);
};
@@ -730,10 +733,12 @@
ArgumentMatcher<String> data,
ArgumentMatcher<Bundle> extras,
Boolean sync,
+ Boolean assumeDelivered,
Integer sendingUser,
Integer processState) {
return argThat(receiverList(manifestReceiverMatcher(intent, activityInfo, compatInfo,
- resultCode, data, extras, sync, sendingUser, processState)));
+ resultCode, data, extras, sync, assumeDelivered,
+ sendingUser, processState)));
}
/**
@@ -749,6 +754,7 @@
ArgumentMatcher<Bundle> extras,
Boolean ordered,
Boolean sticky,
+ Boolean assumeDelivered,
Integer sendingUser,
Integer processState) {
return (test) -> {
@@ -760,6 +766,7 @@
&& matchObject(extras, test.extras)
&& matchElement(ordered, test.ordered)
&& matchElement(sticky, test.sticky)
+ && matchElement(assumeDelivered, test.assumeDelivered)
&& matchElement(sendingUser, test.sendingUser)
&& matchElement(processState, test.processState);
};
@@ -777,10 +784,12 @@
ArgumentMatcher<Bundle> extras,
Boolean ordered,
Boolean sticky,
+ Boolean assumeDelivered,
Integer sendingUser,
Integer processState) {
return argThat(receiverList(registeredReceiverMatcher(receiver, intent, resultCode,
- data, extras, ordered, sticky, sendingUser, processState)));
+ data, extras, ordered, sticky, assumeDelivered,
+ sendingUser, processState)));
}
/**
@@ -833,36 +842,36 @@
final Intent targetedIntent = new Intent(intent);
targetedIntent.setComponent(component);
verify(app.getThread(), mode).scheduleReceiverList(
- manifestReceiver(filterEquals(targetedIntent),
- null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
+ manifestReceiver(filterEquals(targetedIntent),
+ null, null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
}
private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app,
Intent intent, int userId) throws Exception {
verify(app.getThread(), mode).scheduleReceiverList(
- manifestReceiver(filterEqualsIgnoringComponent(intent),
- null, null, null, null, null, null, userId, null));
+ manifestReceiver(filterEqualsIgnoringComponent(intent),
+ null, null, null, null, null, null, null, userId, null));
}
private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app,
int userId) throws Exception {
verify(app.getThread(), mode).scheduleReceiverList(
manifestReceiver(null,
- null, null, null, null, null, null, userId, null));
+ null, null, null, null, null, null, null, userId, null));
}
private void verifyScheduleRegisteredReceiver(ProcessRecord app,
Intent intent) throws Exception {
verify(app.getThread()).scheduleReceiverList(
- registeredReceiver(null, filterEqualsIgnoringComponent(intent),
- null, null, null, null, null, UserHandle.USER_SYSTEM, null));
+ registeredReceiver(null, filterEqualsIgnoringComponent(intent),
+ null, null, null, null, null, null, UserHandle.USER_SYSTEM, null));
}
private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
int userId) throws Exception {
verify(app.getThread(), mode).scheduleReceiverList(
registeredReceiver(null, null,
- null, null, null, null, null, userId, null));
+ null, null, null, null, null, null, userId, null));
}
static final int USER_GUEST = 11;
@@ -1118,10 +1127,11 @@
}
/**
- * Verify that we detect and ANR a wedged process.
+ * Verify that we detect and ANR a wedged process when delivering to a
+ * manifest receiver.
*/
@Test
- public void testWedged() throws Exception {
+ public void testWedged_Manifest() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
ProcessBehavior.WEDGE);
@@ -1135,6 +1145,77 @@
}
/**
+ * Verify that we detect and ANR a wedged process when delivering an ordered
+ * broadcast, and that we deliver final result.
+ */
+ @Test
+ public void testWedged_Registered_Ordered() throws Exception {
+ // Legacy stack doesn't detect these ANRs; likely an oversight
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+ ProcessBehavior.WEDGE);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+ enqueueBroadcast(makeOrderedBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp)), resultTo, null));
+
+ waitForIdle();
+ verify(mAms).appNotResponding(eq(receiverApp), any());
+ verifyScheduleRegisteredReceiver(callerApp, airplane);
+ }
+
+ /**
+ * Verify that we detect and ANR a wedged process when delivering an
+ * unordered broadcast with a {@code resultTo}.
+ */
+ @Test
+ public void testWedged_Registered_ResultTo() throws Exception {
+ // Legacy stack doesn't detect these ANRs; likely an oversight
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+ ProcessBehavior.WEDGE);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp)), resultTo));
+
+ waitForIdle();
+ verify(mAms).appNotResponding(eq(receiverApp), any());
+ verifyScheduleRegisteredReceiver(callerApp, airplane);
+ }
+
+ /**
+ * Verify that we detect and ANR a wedged process when delivering a
+ * broadcast with more than one priority tranche.
+ */
+ @Test
+ public void testWedged_Registered_Prioritized() throws Exception {
+ // Legacy stack doesn't detect these ANRs; likely an oversight
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN,
+ ProcessBehavior.WEDGE);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE,
+ ProcessBehavior.NORMAL);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverGreenApp, 10),
+ makeRegisteredReceiver(receiverBlueApp, 5))));
+
+ waitForIdle();
+ verify(mAms).appNotResponding(eq(receiverGreenApp), any());
+ verifyScheduleRegisteredReceiver(receiverBlueApp, airplane);
+ }
+
+ /**
* Verify that we handle registered receivers in a process that always
* responds with {@link DeadObjectException}, recovering to restart the
* process and deliver their next broadcast.
@@ -1344,11 +1425,11 @@
// Confirm that we saw no registered receiver traffic
final IApplicationThread oldThread = oldApp.getThread();
- verify(oldThread, never()).scheduleRegisteredReceiver(any(),
- any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+ verify(oldThread, never()).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
+ anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
final IApplicationThread newThread = newApp.getThread();
- verify(newThread, never()).scheduleRegisteredReceiver(any(),
- any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+ verify(newThread, never()).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
+ anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
// Confirm that we saw final manifest broadcast
verifyScheduleReceiver(times(1), newApp, airplane,
@@ -1470,22 +1551,22 @@
expectedExtras.putBoolean(PACKAGE_RED, true);
inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver(
filterEqualsIgnoringComponent(airplane), null, null,
- Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+ Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
UserHandle.USER_SYSTEM, null));
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
filterEqualsIgnoringComponent(airplane), null, null,
- Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+ Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
UserHandle.USER_SYSTEM, null));
expectedExtras.putBoolean(PACKAGE_BLUE, true);
inOrder.verify(yellowThread).scheduleReceiverList(manifestReceiver(
filterEqualsIgnoringComponent(airplane), null, null,
- Activity.RESULT_OK, null, bundleEquals(expectedExtras), true,
+ Activity.RESULT_OK, null, bundleEquals(expectedExtras), true, false,
UserHandle.USER_SYSTEM, null));
expectedExtras.putBoolean(PACKAGE_YELLOW, true);
inOrder.verify(redThread).scheduleReceiverList(registeredReceiver(
null, filterEquals(airplane),
Activity.RESULT_OK, null, bundleEquals(expectedExtras), false,
- null, UserHandle.USER_SYSTEM, null));
+ null, true, UserHandle.USER_SYSTEM, null));
// Finally, verify that we thawed the final receiver
verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp),
@@ -1550,22 +1631,22 @@
final InOrder inOrder = inOrder(greenThread, blueThread, redThread);
inOrder.verify(greenThread).scheduleReceiverList(manifestReceiver(
filterEqualsIgnoringComponent(intent), null, null,
- Activity.RESULT_OK, null, null, true, UserHandle.USER_SYSTEM,
+ Activity.RESULT_OK, null, null, true, false, UserHandle.USER_SYSTEM,
null));
if ((intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0) {
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
filterEqualsIgnoringComponent(intent), null, null,
- Activity.RESULT_OK, null, null, true, UserHandle.USER_SYSTEM,
+ Activity.RESULT_OK, null, null, true, false, UserHandle.USER_SYSTEM,
null));
} else {
inOrder.verify(blueThread, never()).scheduleReceiverList(manifestReceiver(
- null, null, null, null,
+ null, null, null, null, null,
null, null, null, null, null));
}
inOrder.verify(redThread).scheduleReceiverList(registeredReceiver(
null, filterEquals(intent),
Activity.RESULT_OK, null, bundleEquals(expectedExtras),
- false, null, UserHandle.USER_SYSTEM, null));
+ false, null, true, UserHandle.USER_SYSTEM, null));
}
/**
@@ -1588,7 +1669,7 @@
verify(callerThread).scheduleReceiverList(registeredReceiver(
null, filterEquals(airplane),
Activity.RESULT_OK, null, bundleEquals(orderedExtras), false,
- null, UserHandle.USER_SYSTEM, null));
+ null, true, UserHandle.USER_SYSTEM, null));
}
/**
@@ -1609,7 +1690,7 @@
verify(callerThread).scheduleReceiverList(registeredReceiver(
null, filterEquals(airplane),
Activity.RESULT_OK, null, null, false,
- null, UserHandle.USER_SYSTEM, null));
+ null, true, UserHandle.USER_SYSTEM, null));
}
/**
@@ -1775,26 +1856,26 @@
// First broadcast is canceled
inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null,
filterAndExtrasEquals(timezoneFirst), Activity.RESULT_CANCELED, null,
- null, false, null, UserHandle.USER_SYSTEM, null));
+ null, false, null, true, UserHandle.USER_SYSTEM, null));
// We deliver second broadcast to app
timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN);
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
filterAndExtrasEquals(timezoneSecond),
- null, null, null, null, null, true, null, null));
+ null, null, null, null, null, true, false, null, null));
// Second broadcast is finished
timezoneSecond.setComponent(null);
inOrder.verify(callerThread).scheduleReceiverList(registeredReceiver(null,
filterAndExtrasEquals(timezoneSecond), Activity.RESULT_OK, null,
- null, false, null, UserHandle.USER_SYSTEM, null));
+ null, false, null, true, UserHandle.USER_SYSTEM, null));
// Since we "replaced" the first broadcast in its original position,
// only now do we see the airplane broadcast
airplane.setClassName(PACKAGE_BLUE, CLASS_RED);
inOrder.verify(blueThread).scheduleReceiverList(manifestReceiver(
filterEquals(airplane),
- null, null, null, null, null, false, null, null));
+ null, null, null, null, null, false, false, null, null));
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1367af8..f13de12 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -20,25 +20,31 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
import androidx.test.annotation.UiThreadTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.internal.widget.LockSettingsInternal;
import com.android.server.ExtendedMockitoTestCase;
import com.android.server.LocalServices;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerService.UserData;
+import com.android.server.storage.DeviceStorageMonitorInternal;
import org.junit.After;
import org.junit.Before;
@@ -86,6 +92,10 @@
private @Mock PackageManagerService mMockPms;
private @Mock UserDataPreparer mMockUserDataPreparer;
private @Mock ActivityManagerInternal mActivityManagerInternal;
+ private @Mock DeviceStorageMonitorInternal mDeviceStorageMonitorInternal;
+ private @Mock StorageManager mStorageManager;
+ private @Mock LockSettingsInternal mLockSettingsInternal;
+ private @Mock PackageManagerInternal mPackageManagerInternal;
/**
* Reference to the {@link UserManagerService} being tested.
@@ -101,7 +111,8 @@
protected void initializeSession(StaticMockitoSessionBuilder builder) {
builder
.spyStatic(UserManager.class)
- .spyStatic(LocalServices.class);
+ .spyStatic(LocalServices.class)
+ .mockStatic(Settings.Global.class);
}
@Before
@@ -112,6 +123,14 @@
// Called when WatchedUserStates is constructed
doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
+ // Called when creating new users
+ when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
+ mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
+ when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+ mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
+ mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
+ doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
+
// Must construct UserManagerService in the UiThread
mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
mPackagesLock, mRealContext.getDataDir(), mUsers);
@@ -223,6 +242,87 @@
.that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
}
+ @Test
+ public void testSetBootUser_SuppliedUserIsSwitchable() throws Exception {
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+
+ mUms.setBootUser(OTHER_USER_ID);
+
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ }
+
+ @Test
+ public void testSetBootUser_NotHeadless_SuppliedUserIsNotSwitchable() throws Exception {
+ setSystemUserHeadless(false);
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+ addDefaultProfileAndParent();
+
+ mUms.setBootUser(PROFILE_USER_ID);
+
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void testSetBootUser_Headless_SuppliedUserIsNotSwitchable() throws Exception {
+ setSystemUserHeadless(true);
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+ addDefaultProfileAndParent();
+
+ mUms.setBootUser(PROFILE_USER_ID);
+
+ // Boot user not switchable so return most recently in foreground.
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ }
+
+ @Test
+ public void testGetBootUser_NotHeadless_ReturnsSystemUser() throws Exception {
+ setSystemUserHeadless(false);
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void testGetBootUser_Headless_ReturnsMostRecentlyInForeground() throws Exception {
+ setSystemUserHeadless(true);
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+ assertWithMessage("getBootUser")
+ .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+ }
+
+ @Test
+ public void testGetBootUser_Headless_UserCreatedIfOnlySystemUserExists() throws Exception {
+ setSystemUserHeadless(true);
+
+ int bootUser = mUmi.getBootUser();
+
+ assertWithMessage("getStartingUser")
+ .that(bootUser).isNotEqualTo(UserHandle.USER_SYSTEM);
+
+ UserData newUser = mUsers.get(bootUser);
+ assertWithMessage("New boot user is a full user")
+ .that(newUser.info.isFull()).isTrue();
+ assertWithMessage("New boot user is an admin user")
+ .that(newUser.info.isAdmin()).isTrue();
+ assertWithMessage("New boot user is the main user")
+ .that(newUser.info.isMain()).isTrue();
+ }
+
private void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
@@ -248,7 +348,7 @@
private void addUser(@UserIdInt int userId) {
TestUserData userData = new TestUserData(userId);
-
+ userData.info.flags = UserInfo.FLAG_FULL;
addUserData(userData);
}
@@ -277,6 +377,23 @@
mUsers.put(userData.info.id, userData);
}
+ private void setSystemUserHeadless(boolean headless) {
+ UserData systemUser = mUsers.get(UserHandle.USER_SYSTEM);
+ if (headless) {
+ systemUser.info.flags &= ~UserInfo.FLAG_FULL;
+ systemUser.info.userType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
+ } else {
+ systemUser.info.flags |= UserInfo.FLAG_FULL;
+ systemUser.info.userType = UserManager.USER_TYPE_FULL_SYSTEM;
+ }
+ doReturn(headless).when(() -> UserManager.isHeadlessSystemUserMode());
+ }
+
+ private void setLastForegroundTime(@UserIdInt int userId, long timeMillis) {
+ UserData userData = mUsers.get(userId);
+ userData.mLastEnteredForegroundTimeMillis = timeMillis;
+ }
+
private static final class TestUserData extends UserData {
@SuppressWarnings("deprecation")
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 c08f6bf..a924207 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -306,12 +306,13 @@
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
- spyOn(mService.mLastWallpaper.connection);
- doReturn(true).when(mService.mLastWallpaper.connection).isUsableDisplay(any());
+ spyOn(mService.mWallpaperDisplayHelper);
+ doReturn(true).when(mService.mWallpaperDisplayHelper)
+ .isUsableDisplay(any(Display.class), mService.mLastWallpaper.connection.mClientUid);
mService.mLastWallpaper.connection.attachEngine(mock(IWallpaperEngine.class),
DEFAULT_DISPLAY);
- WallpaperManagerService.WallpaperConnection.DisplayConnector connector =
+ WallpaperManagerService.DisplayConnector connector =
mService.mLastWallpaper.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY);
mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
@@ -521,7 +522,7 @@
}
private void verifyDisplayData() {
- mService.forEachDisplayData(data -> {
+ mService.mWallpaperDisplayHelper.forEachDisplayData(data -> {
assertTrue("Display width must larger than maximum screen size",
data.mWidth >= DISPLAY_SIZE_DIMENSION);
assertTrue("Display height must larger than maximum screen size",
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e54a48b..1298e7b 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -1292,10 +1292,11 @@
unlockSystemUser();
try {
mAms.hasFeatures(
- null, // response
- AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
- new String[] {"feature1", "feature2"}, // features
- "testPackage"); // opPackageName
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ new String[] {"feature1", "feature2"}, // features
+ 0, // userId
+ "testPackage"); // opPackageName
fail("IllegalArgumentException expected. But no exception was thrown.");
} catch (IllegalArgumentException e) {
// IllegalArgumentException is expected.
@@ -1307,10 +1308,11 @@
unlockSystemUser();
try {
mAms.hasFeatures(
- mMockAccountManagerResponse, // response
- null, // account
- new String[] {"feature1", "feature2"}, // features
- "testPackage"); // opPackageName
+ mMockAccountManagerResponse, // response
+ null, // account
+ new String[] {"feature1", "feature2"}, // features
+ 0, // userId
+ "testPackage"); // opPackageName
fail("IllegalArgumentException expected. But no exception was thrown.");
} catch (IllegalArgumentException e) {
// IllegalArgumentException is expected.
@@ -1325,6 +1327,7 @@
mMockAccountManagerResponse, // response
AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
null, // features
+ 0, // userId
"testPackage"); // opPackageName
fail("IllegalArgumentException expected. But no exception was thrown.");
} catch (IllegalArgumentException e) {
@@ -1341,6 +1344,7 @@
response, // response
AccountManagerServiceTestFixtures.ACCOUNT_ERROR, // account
AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+ 0, // userId
"testPackage"); // opPackageName
waitForLatch(latch);
verify(mMockAccountManagerResponse).onError(
@@ -1357,6 +1361,7 @@
response, // response
AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+ 0, // userId
"testPackage"); // opPackageName
waitForLatch(latch);
verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index d4e3d44..0dd60b8 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -95,7 +96,7 @@
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
doNothing().when(mAccountManagerInternal).addOnAppPermissionChangeListener(any());
when(mJobSchedulerInternal.getSystemScheduledPendingJobs()).thenReturn(new ArrayList<>());
- mSyncManager = new SyncManagerWithMockedServices(mContext, true);
+ mSyncManager = spy(new SyncManagerWithMockedServices(mContext, true));
}
public void testSyncExtrasEquals_WithNull() throws Exception {
@@ -233,6 +234,7 @@
}
public void testShouldDisableSync() {
+ doReturn(true).when(mSyncManager).isContactSharingAllowedForCloneProfile();
UserInfo primaryUserInfo = createUserInfo("primary", 0 /* id */, 0 /* groupId */,
UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
UserInfo cloneUserInfo = createUserInfo("clone", 10 /* id */, 0 /* groupId */,
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 61c3f13..210aeef 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -7396,7 +7396,7 @@
verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any());
// Now the user should see a warning notification.
verify(getServices().notificationManager, times(1))
- .notify(anyInt(), any());
+ .notifyAsUser(any(), anyInt(), any(), any());
// Apps shouldn't be suspended yet.
verifyZeroInteractions(getServices().ipackageManager);
clearInvocations(getServices().alarmManager);
@@ -7410,7 +7410,7 @@
verifyZeroInteractions(getServices().alarmManager);
// Now the user should see a notification about suspended apps.
verify(getServices().notificationManager, times(1))
- .notify(anyInt(), any());
+ .notifyAsUser(any(), anyInt(), any(), any());
// Verify that the apps are suspended.
verify(getServices().ipackageManager, times(1)).setPackagesSuspendedAsUser(
any(), eq(true), any(), any(), any(), any(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
index 96302b9..299f153 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
@@ -25,6 +25,7 @@
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
@@ -63,6 +64,7 @@
private MatrixCursor mContactsLookupCursor;
private MatrixCursor mPhoneCursor;
private ContactsQueryHelper mHelper;
+ private ContactsContentProvider contentProvider;
@Before
public void setUp() {
@@ -73,7 +75,7 @@
mPhoneCursor = new MatrixCursor(PHONE_COLUMNS);
MockContentResolver contentResolver = new MockContentResolver();
- ContactsContentProvider contentProvider = new ContactsContentProvider();
+ contentProvider = new ContactsContentProvider();
contentProvider.registerCursor(Contacts.CONTENT_URI, mContactsCursor);
contentProvider.registerCursor(
ContactsContract.PhoneLookup.CONTENT_FILTER_URI, mContactsLookupCursor);
@@ -89,6 +91,14 @@
}
@Test
+ public void testQueryException_returnsFalse() {
+ contentProvider.setThrowException(true);
+
+ Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
+ assertFalse(mHelper.query(contactUri.toString()));
+ }
+
+ @Test
public void testQueryWithUri() {
mContactsCursor.addRow(new Object[] {
/* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 1,
@@ -168,10 +178,15 @@
private class ContactsContentProvider extends MockContentProvider {
private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>();
+ private boolean throwException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (throwException) {
+ throw new SQLiteException();
+ }
+
for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) {
if (uri.isPathPrefixMatch(prefixUri)) {
return mUriPrefixToCursorMap.get(prefixUri);
@@ -180,6 +195,10 @@
return mUriPrefixToCursorMap.get(uri);
}
+ public void setThrowException(boolean throwException) {
+ this.throwException = throwException;
+ }
+
private void registerCursor(Uri uriPrefix, Cursor cursor) {
mUriPrefixToCursorMap.put(uriPrefix, cursor);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index bf2faac..3135215 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -137,6 +137,13 @@
return this;
}
+ public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(perUidModemModel);
+ }
+ return this;
+ }
+
/** Call only after setting the power profile information. */
public BatteryUsageStatsRule initMeasuredEnergyStatsLocked() {
return initMeasuredEnergyStatsLocked(new String[0]);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 65e6486..d059472 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -574,8 +574,10 @@
}
@Test
- public void testMeasuredEnergyBasedModel() {
+ public void testMeasuredEnergyBasedModel_mobileRadioActiveTimeModel() {
mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .setPerUidModemModel(
+ BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME)
.initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
@@ -637,6 +639,263 @@
.isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
+
+
+ @Test
+ public void testMeasuredEnergyBasedModel_modemActivityInfoRxTxModel() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+ .setPerUidModemModel(
+ BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX)
+ .initMeasuredEnergyStatsLocked();
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ // Scan for a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+ TelephonyManager.SIM_STATE_READY,
+ 2000, 2000);
+
+ // Found a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+ 5000, 5000);
+
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
+ // Note cell signal strength
+ SignalStrength signalStrength = mock(SignalStrength.class);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 8_000_000_000L, APP_UID, 8000, 8000);
+
+ // Note established network
+ stats.noteNetworkInterfaceForTransports("cellular",
+ new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 9_500_000_000L, APP_UID2, 9500, 9500);
+
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+ // Note application network activity
+ NetworkStats networkStats = new NetworkStats(10000, 1)
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 300, 10, 100))
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 2000, 30, 111));
+ mStatsRule.setNetworkStats(networkStats);
+
+ ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ new int[]{10, 11, 12, 13, 14}, 15);
+ ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ new int[]{20, 21, 22, 23, 24}, 25);
+ ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+ new int[]{30, 31, 32, 33, 34}, 35);
+ ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+ new int[]{40, 41, 42, 43, 44}, 45);
+ ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+ new int[]{50, 51, 52, 53, 54}, 55);
+ ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+ new int[]{60, 61, 62, 63, 64}, 65);
+
+ ActivityStatsTechSpecificInfo[] ratInfos =
+ new ActivityStatsTechSpecificInfo[]{cdmaInfo, lteInfo, nrLowFreqInfo, nrMidFreqInfo,
+ nrHighFreqInfo, nrMmwaveFreqInfo};
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, ratInfos);
+ stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000,
+ mNetworkStatsManager);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ MobileRadioPowerCalculator calculator =
+ new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(2.77778);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+ // CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
+ // LTE [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [800, 1200, 1600, 2000, 2400, 2000] mA . [20, 21, 22, 23, 24, 25] ms = 230000 mA-ms
+ // 5G Low Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // (nrFrequency="LOW" values was not defined so fall back to nrFrequency="DEFAULT")
+ // [999, 1333, 1888, 2222, 2666, 2222] mA . [30, 31, 32, 33, 34, 35] ms = 373449 mA-ms
+ // 5G Mid Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // (nrFrequency="MID" values was not defined so fall back to nrFrequency="DEFAULT")
+ // [999, 1333, 1888, 2222, 2666, 2222] mA . [40, 41, 42, 43, 44, 45] ms = 486749 mA-ms
+ // 5G High Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [1818, 2727, 3636, 4545, 5454, 2727] mA . [50, 51, 52, 53, 54, 55] ms = 1104435 mA-ms
+ // 5G Mmwave Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [2345, 3456, 4567, 5678, 6789, 3456] mA . [60, 61, 62, 63, 64, 65] ms = 1651520 mA-ms
+ // _________________
+ // = 3957753 mA-ms estimated active consumption
+ //
+ // Idle drain rate * idle duration
+ // 360 mA * 3000 ms = 1080000 mA-ms
+ // Sleep drain rate * sleep duration
+ // 70 mA * 2000 ms = 140000 mA-ms
+ // _________________
+ // = 5177753 mA-ms estimated total consumption
+ //
+ // 2.77778 mA-h measured total consumption * 3957753 / 5177753 = 2.123268 mA-h
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(2.123268);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+ // 240 ms Rx Time, 1110 ms Tx Time, 1350 ms active time
+ // 150 App 1 Rx Packets, 10 App 1 Tx packets
+ // 50 App 2 Rx Packets, 30 App 2 Tx packets
+ // 200 total Rx Packets, 40 total Tx packets
+ // 623985 mA-ms Rx consumption, 3333768 mA-ms Tx consumption
+ //
+ // Rx Power consumption * Ratio of App1 / Total Rx Packets:
+ // 623985 * 150 / 200 = 467988.75 mA-ms App 1 Rx Power Consumption
+ //
+ // App 1 Tx Packets + App 1 Rx Packets * Ratio of Tx / Total active time
+ // 10 + 150 * 1110 / 1350 = 133.3333 Estimated App 1 Rx/Tx Packets during Tx
+ // Total Tx Packets + Total Rx Packets * Ratio of Tx / Total active time
+ // 40 + 200 * 1110 / 1350 = 204.44444 Estimated Total Rx/Tx Packets during Tx
+ // Tx Power consumption * Ratio of App 1 / Total Estimated Tx Packets:
+ // 3333768 * 133.33333 / 204.44444 = 2174196.52174 mA-ms App 1 Tx Power Consumption
+ //
+ // Total App Power consumption * Ratio of App 1 / Total Estimated Power Consumption
+ // 2.123268 * (467988.75 + 2174196.52174) / 3957753 = 1.41749 App 1 Power Consumption
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.41749);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+ // Rest should go to the other app
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.705778);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ }
+
+ @Test
+ public void testMeasuredEnergyBasedModel_modemActivityInfoRxTxModel_legacyPowerProfile() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .setPerUidModemModel(
+ BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX)
+ .initMeasuredEnergyStatsLocked();
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ // Scan for a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+ TelephonyManager.SIM_STATE_READY,
+ 2000, 2000);
+
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
+ // Found a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+ 5000, 5000);
+
+ // Note cell signal strength
+ SignalStrength signalStrength = mock(SignalStrength.class);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 8_000_000_000L, APP_UID, 8000, 8000);
+
+ // Note established network
+ stats.noteNetworkInterfaceForTransports("cellular",
+ new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+ // Note application network activity
+ NetworkStats networkStats = new NetworkStats(10000, 1)
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
+ mStatsRule.setNetworkStats(networkStats);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[]{100, 200, 300, 400, 500}, 600);
+ stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000, mNetworkStatsManager);
+
+ mStatsRule.setTime(12_000, 12_000);
+
+ MobileRadioPowerCalculator calculator =
+ new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(2.77778);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ // Estimated Rx/Tx modem consumption = 0.94 mAh
+ // Estimated total modem consumption = 1.27888 mAh
+ // 2.77778 * 0.94 / 1.27888 = 2.04170 mAh
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(2.04170);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(2.04170);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ }
+
@Test
public void testMeasuredEnergyBasedModel_byProcessState() {
mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 19d2639..f803355 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -213,6 +213,13 @@
return this;
}
+ @GuardedBy("this")
+ public MockBatteryStatsImpl setPerUidModemModel(int perUidModemModel) {
+ mConstants.PER_UID_MODEM_MODEL = perUidModemModel;
+ mConstants.onChange();
+ return this;
+ }
+
public int getAndClearExternalStatsSyncFlags() {
final int flags = mExternalStatsSync.flags;
mExternalStatsSync.flags = 0;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 4ad8516..8cc362c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -280,7 +280,7 @@
}
@Test
- public void testStartRecording_notifiesCallback() {
+ public void testStartRecording_notifiesCallback_taskSession() {
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -292,6 +292,18 @@
}
@Test
+ public void testStartRecording_notifiesCallback_displaySession() {
+ // WHEN a recording is ongoing.
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // THEN the visibility change callback is notified.
+ verify(mMediaProjectionManagerWrapper)
+ .notifyActiveProjectionCapturedContentVisibilityChanged(true);
+ }
+
+ @Test
public void testOnVisibleRequestedChanged_notifiesCallback() {
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 3eb7fe3..352e498 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -201,27 +201,6 @@
}
@Test
- public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() {
- mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
- setUpDisplaySizeWithApp(2000, 1000);
- prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
- mActivity.info.setMinAspectRatio(1.2f);
- mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- // Translucent Activity
- final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
- .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
- .setMinAspectRatio(1.1f)
- .setMaxAspectRatio(3f)
- .build();
- doReturn(false).when(translucentActivity).fillsParent();
- mTask.addChild(translucentActivity);
- // We check bounds
- final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
- final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
- assertNotEquals(opaqueBounds, translucentRequestedBounds);
- }
-
- @Test
public void testApplyStrategyToMultipleTranslucentActivities() {
mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
setUpDisplaySizeWithApp(2000, 1000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 035d73d..dbd7a4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -819,6 +819,38 @@
}
@Test
+ public void testApplyTransaction_createTaskFragment_overrideBounds() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activityAtBottom = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activityAtBottom.info.applicationInfo.uid = uid;
+ activityAtBottom.getTask().effectiveUid = uid;
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .createActivityCount(1)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final IBinder fragmentToken1 = new Binder();
+ final Rect bounds = new Rect(100, 100, 500, 1000);
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken1, activityAtBottom.token)
+ .setPairedActivityToken(activityAtBottom.token)
+ .setInitialBounds(bounds)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Successfully created a TaskFragment.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+ fragmentToken1);
+ assertNotNull(taskFragment);
+ // The relative embedded bounds is updated to the initial requested bounds.
+ assertEquals(bounds, taskFragment.getRelativeEmbeddedBounds());
+ }
+
+ @Test
public void testApplyTransaction_createTaskFragment_withPairedActivityToken() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activityAtBottom = createActivityRecord(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 6bce959..1f60e79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -552,7 +552,12 @@
win.applyWithNextDraw(t -> handledT[0] = t);
assertTrue(win.useBLASTSync());
final SurfaceControl.Transaction drawT = new StubTransaction();
+ final SurfaceControl.Transaction currT = win.getSyncTransaction();
+ clearInvocations(currT);
+ win.mWinAnimator.mLastHidden = true;
assertTrue(win.finishDrawing(drawT, Integer.MAX_VALUE));
+ // The draw transaction should be merged to current transaction even if the state is hidden.
+ verify(currT).merge(eq(drawT));
assertEquals(drawT, handledT[0]);
assertFalse(win.useBLASTSync());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 4d6d320..b8536f9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -205,9 +205,15 @@
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
- mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, getDetectorType(),
- mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
- mVoiceInteractorIdentity.attributionTag);
+ if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager,
+ getDetectorType(),
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag);
+ } else {
+ mHotwordAudioStreamCopier = null;
+ }
+
mScheduledExecutorService = scheduledExecutorService;
mDebugHotwordLogging = logging;
@@ -236,9 +242,12 @@
future.complete(null);
if (mUpdateStateAfterStartFinished.getAndSet(true)) {
Slog.w(TAG, "call callback after timeout");
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ if (getDetectorType()
+ != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT,
mVoiceInteractionServiceUid);
+ }
return;
}
Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle);
@@ -246,27 +255,37 @@
int initResultMetricsResult = statusResultPair.second;
try {
mCallback.onStatusReported(status);
- HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
- initResultMetricsResult, mVoiceInteractionServiceUid);
+ if (getDetectorType()
+ != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
+ initResultMetricsResult, mVoiceInteractionServiceUid);
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status: " + e);
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
- METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
- mVoiceInteractionServiceUid);
+ if (getDetectorType()
+ != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ }
}
}
};
try {
service.updateState(options, sharedMemory, statusCallback);
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
- HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
- mVoiceInteractionServiceUid);
+ if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
+ mVoiceInteractionServiceUid);
+ }
} catch (RemoteException e) {
// TODO: (b/181842909) Report an error to voice interactor
Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
- HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
- mVoiceInteractionServiceUid);
+ if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ }
}
return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}).whenComplete((res, err) -> {
@@ -277,13 +296,17 @@
}
try {
mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
- HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
- METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid);
+ if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
+ METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid);
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
- METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
- mVoiceInteractionServiceUid);
+ if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ }
}
} else if (err != null) {
Slog.w(TAG, "Failed to update state: " + err);
@@ -315,9 +338,11 @@
@SuppressWarnings("GuardedBy")
void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
Instant lastRestartInstant) {
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
- HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
- mVoiceInteractionServiceUid);
+ if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
+ mVoiceInteractionServiceUid);
+ }
// Prevent doing the init late, so restart is handled equally to a clean process start.
// TODO(b/191742511): this logic needs a test
if (!mUpdateStateAfterStartFinished.get() && Instant.now().minus(
@@ -399,9 +424,11 @@
callback.onError();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to report onError status: " + ex);
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
- HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
- mVoiceInteractionServiceUid);
+ if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ }
}
} finally {
synchronized (mLock) {
@@ -427,7 +454,8 @@
throws RemoteException {
synchronized (mLock) {
mPerformingExternalSourceHotwordDetection = false;
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ HotwordMetricsLogger.writeDetectorEvent(
+ getDetectorType(),
METRICS_EXTERNAL_SOURCE_REJECTED,
mVoiceInteractionServiceUid);
mScheduledExecutorService.schedule(
@@ -454,7 +482,8 @@
throws RemoteException {
synchronized (mLock) {
mPerformingExternalSourceHotwordDetection = false;
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ HotwordMetricsLogger.writeDetectorEvent(
+ getDetectorType(),
METRICS_EXTERNAL_SOURCE_DETECTED,
mVoiceInteractionServiceUid);
mScheduledExecutorService.schedule(
@@ -540,9 +569,11 @@
mCallback.onError(status);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report onError status: " + e);
- HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
- HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
- mVoiceInteractionServiceUid);
+ if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ }
}
}
@@ -679,6 +710,8 @@
return HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP;
} else if (this instanceof SoftwareTrustedHotwordDetectorSession) {
return HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE;
+ } else if (this instanceof VisualQueryDetectorSession) {
+ return HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR;
}
Slog.v(TAG, "Unexpected detector type");
return -1;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index ad84f00..cb5b930 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -71,6 +71,8 @@
@GuardedBy("mLock")
private boolean mValidatingDspTrigger = false;
+ @GuardedBy("mLock")
+ private HotwordRejectedResult mLastHotwordRejectedResult = null;
DspTrustedHotwordDetectorSession(
@NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
@@ -110,7 +112,8 @@
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
mVoiceInteractionServiceUid);
if (!mValidatingDspTrigger) {
- Slog.i(TAG, "Ignoring #onDetected due to a process restart");
+ Slog.i(TAG, "Ignoring #onDetected due to a process restart or previous"
+ + " #onRejected result = " + mLastHotwordRejectedResult);
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
@@ -173,6 +176,7 @@
}
mValidatingDspTrigger = false;
externalCallback.onRejected(result);
+ mLastHotwordRejectedResult = result;
if (mDebugHotwordLogging && result != null) {
Slog.i(TAG, "Egressed rejected result: " + result);
}
@@ -181,6 +185,7 @@
};
mValidatingDspTrigger = true;
+ mLastHotwordRejectedResult = null;
mRemoteDetectionService.run(service -> {
// We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
// the callback before timeout value. In order to reduce the latency impact between
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index d501af7..665d5e7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -52,6 +52,8 @@
import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.ISandboxedDetectionService;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.VisualQueryDetectionService;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
import android.speech.IRecognitionServiceManager;
import android.util.Slog;
@@ -74,7 +76,8 @@
import java.util.function.Function;
/**
- * A class that provides the communication with the HotwordDetectionService.
+ * A class that provides the communication with the {@link HotwordDetectionService} and
+ * {@link VisualQueryDetectionService}.
*/
final class HotwordDetectionConnection {
private static final String TAG = "HotwordDetectionConnection";
@@ -90,7 +93,8 @@
@Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
@NonNull private final ServiceConnectionFactory mHotwordDetectionServiceConnectionFactory;
- private final int mDetectorType;
+ @NonNull private final ServiceConnectionFactory mVisualQueryDetectionServiceConnectionFactory;
+ private int mDetectorType;
/**
* Time after which each HotwordDetectionService process is stopped and replaced by a new one.
* 0 indicates no restarts.
@@ -100,9 +104,11 @@
final Object mLock;
final int mVoiceInteractionServiceUid;
final ComponentName mHotwordDetectionComponentName;
+ final ComponentName mVisualQueryDetectionComponentName;
final int mUser;
final Context mContext;
volatile HotwordDetectionServiceIdentity mIdentity;
+ //TODO: add similar identity for visual query service for the use of content capturing
private Instant mLastRestartInstant;
private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
@@ -112,6 +118,7 @@
@Nullable
private final Identity mVoiceInteractorIdentity;
@NonNull private ServiceConnection mRemoteHotwordDetectionService;
+ @NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
private IBinder mAudioFlinger;
@GuardedBy("mLock")
private boolean mDebugHotwordLogging = false;
@@ -126,26 +133,39 @@
new SparseArray<>();
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
- Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName, int userId,
+ Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName,
+ ComponentName visualQueryDetectionServiceName, int userId,
boolean bindInstantServiceAllowed, int detectorType) {
mLock = lock;
mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
mHotwordDetectionComponentName = hotwordDetectionServiceName;
+ mVisualQueryDetectionComponentName = visualQueryDetectionServiceName;
mUser = userId;
mDetectorType = detectorType;
mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
KEY_RESTART_PERIOD_IN_SECONDS, 0);
+
final Intent hotwordDetectionServiceIntent =
new Intent(HotwordDetectionService.SERVICE_INTERFACE);
hotwordDetectionServiceIntent.setComponent(mHotwordDetectionComponentName);
+
+ final Intent visualQueryDetectionServiceIntent =
+ new Intent(VisualQueryDetectionService.SERVICE_INTERFACE);
+ visualQueryDetectionServiceIntent.setComponent(mVisualQueryDetectionComponentName);
+
initAudioFlingerLocked();
mHotwordDetectionServiceConnectionFactory =
new ServiceConnectionFactory(hotwordDetectionServiceIntent,
bindInstantServiceAllowed);
- mRemoteHotwordDetectionService = mHotwordDetectionServiceConnectionFactory.createLocked();
+
+ mVisualQueryDetectionServiceConnectionFactory =
+ new ServiceConnectionFactory(visualQueryDetectionServiceIntent,
+ bindInstantServiceAllowed);
+
+
mLastRestartInstant = Instant.now();
if (mReStartPeriodSeconds <= 0) {
@@ -157,9 +177,11 @@
Slog.v(TAG, "Time to restart the process, TTL has passed");
synchronized (mLock) {
restartProcessLocked();
- HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
- HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
- mVoiceInteractionServiceUid);
+ if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
+ HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
+ mVoiceInteractionServiceUid);
+ }
}
}, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
}
@@ -193,9 +215,11 @@
// We restart the process instead of simply sending over the new binder, to avoid race
// conditions with audio reading in the service.
restartProcessLocked();
- HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
- HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
- mVoiceInteractionServiceUid);
+ if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
+ HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
+ mVoiceInteractionServiceUid);
+ }
}
}
@@ -208,9 +232,8 @@
});
mDetectorSessions.clear();
mDebugHotwordLogging = false;
- mRemoteHotwordDetectionService.unbind();
- LocalServices.getService(PermissionManagerServiceInternal.class)
- .setHotwordDetectionServiceProvider(null);
+ unbindVisualQueryDetectionService();
+ unbindHotwordDetectionService();
if (mIdentity != null) {
removeServiceUidForAudioPolicy(mIdentity.getIsolatedUid());
}
@@ -223,6 +246,21 @@
}
}
+ private void unbindVisualQueryDetectionService() {
+ if (mRemoteVisualQueryDetectionService != null) {
+ mRemoteVisualQueryDetectionService.unbind();
+ //TODO: Set visual query detection service provider to null
+ }
+ }
+
+ private void unbindHotwordDetectionService() {
+ if (mRemoteHotwordDetectionService != null) {
+ mRemoteHotwordDetectionService.unbind();
+ LocalServices.getService(PermissionManagerServiceInternal.class)
+ .setHotwordDetectionServiceProvider(null);
+ }
+ }
+
@SuppressWarnings("GuardedBy")
void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
@NonNull IBinder token) {
@@ -253,6 +291,34 @@
session.startListeningFromMicLocked(audioFormat, callback);
}
+ /**
+ * This method is only used by VisualQueryDetector.
+ */
+ void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "startPerceivingLocked");
+ }
+ final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
+ if (session == null) {
+ return;
+ }
+ session.startPerceivingLocked(callback);
+ }
+
+ /**
+ * This method is only used by VisaulQueryDetector.
+ */
+ void stopPerceivingLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "stopPerceivingLocked");
+ }
+ final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
+ if (session == null) {
+ return;
+ }
+ session.stopPerceivingLocked();
+ }
+
public void startListeningFromExternalSourceLocked(
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@@ -342,6 +408,10 @@
}
}
+ void setDetectorType(int detectorType) {
+ mDetectorType = detectorType;
+ }
+
private void clearDebugHotwordLoggingTimeoutLocked() {
if (mDebugHotwordLoggingTimeoutFuture != null) {
mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */ true);
@@ -353,24 +423,41 @@
private void restartProcessLocked() {
// TODO(b/244598068): Check HotwordAudioStreamManager first
Slog.v(TAG, "Restarting hotword detection process");
+
ServiceConnection oldHotwordConnection = mRemoteHotwordDetectionService;
+ ServiceConnection oldVisualQueryDetectionConnection = mRemoteVisualQueryDetectionService;
HotwordDetectionServiceIdentity previousIdentity = mIdentity;
+ //TODO: Add previousIdentity for visual query detection service
mLastRestartInstant = Instant.now();
// Recreate connection to reset the cache.
+
mRemoteHotwordDetectionService = mHotwordDetectionServiceConnectionFactory.createLocked();
+ mRemoteVisualQueryDetectionService =
+ mVisualQueryDetectionServiceConnectionFactory.createLocked();
Slog.v(TAG, "Started the new process, dispatching processRestarted to detector");
runForEachDetectorSessionLocked((session) -> {
- session.updateRemoteSandboxedDetectionServiceLocked(mRemoteHotwordDetectionService);
+ HotwordDetectionConnection.ServiceConnection newRemoteService =
+ (session instanceof VisualQueryDetectorSession)
+ ? mRemoteVisualQueryDetectionService : mRemoteHotwordDetectionService;
+ session.updateRemoteSandboxedDetectionServiceLocked(newRemoteService);
session.informRestartProcessLocked();
});
if (DEBUG) {
Slog.i(TAG, "processRestarted is dispatched done, unbinding from the old process");
}
- oldHotwordConnection.ignoreConnectionStatusEvents();
- oldHotwordConnection.unbind();
+ if (oldHotwordConnection != null) {
+ oldHotwordConnection.ignoreConnectionStatusEvents();
+ oldHotwordConnection.unbind();
+ }
+
+ if (oldVisualQueryDetectionConnection != null) {
+ oldVisualQueryDetectionConnection.ignoreConnectionStatusEvents();
+ oldVisualQueryDetectionConnection.unbind();
+ }
+
if (previousIdentity != null) {
removeServiceUidForAudioPolicy(previousIdentity.getIsolatedUid());
}
@@ -438,9 +525,14 @@
synchronized (mLock) {
pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
pw.print(prefix); pw.print("mBound=");
- pw.println(mRemoteHotwordDetectionService.isBound());
+ pw.println(mRemoteHotwordDetectionService != null
+ && mRemoteHotwordDetectionService.isBound());
+ pw.println(mRemoteVisualQueryDetectionService != null
+ && mRemoteHotwordDetectionService != null
+ && mRemoteHotwordDetectionService.isBound());
pw.print(prefix); pw.print("mRestartCount=");
pw.println(mHotwordDetectionServiceConnectionFactory.mRestartCount);
+ pw.println(mVisualQueryDetectionServiceConnectionFactory.mRestartCount);
pw.print(prefix); pw.print("mLastRestartInstant="); pw.println(mLastRestartInstant);
pw.print(prefix); pw.print("mDetectorType=");
pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
@@ -489,11 +581,11 @@
private boolean mIsLoggedFirstConnect = false;
ServiceConnection(@NonNull Context context,
- @NonNull Intent intent, int bindingFlags, int userId,
+ @NonNull Intent serviceIntent, int bindingFlags, int userId,
@Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface,
int instanceNumber) {
- super(context, intent, bindingFlags, userId, binderAsInterface);
- this.mIntent = intent;
+ super(context, serviceIntent, bindingFlags, userId, binderAsInterface);
+ this.mIntent = serviceIntent;
this.mBindingFlags = bindingFlags;
this.mInstanceNumber = instanceNumber;
}
@@ -512,14 +604,18 @@
mIsBound = connected;
if (!connected) {
- HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
- HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
- mVoiceInteractionServiceUid);
+ if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
+ mVoiceInteractionServiceUid);
+ }
} else if (!mIsLoggedFirstConnect) {
mIsLoggedFirstConnect = true;
- HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
- HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
- mVoiceInteractionServiceUid);
+ if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
+ mVoiceInteractionServiceUid);
+ }
}
}
}
@@ -539,6 +635,7 @@
return;
}
}
+ //TODO(b265535257): report error to either service only.
synchronized (HotwordDetectionConnection.this.mLock) {
runForEachDetectorSessionLocked((session) -> {
session.reportErrorLocked(
@@ -546,35 +643,46 @@
});
}
// Can improve to log exit reason if needed
- HotwordMetricsLogger.writeKeyphraseTriggerEvent(
- mDetectorType,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
- mVoiceInteractionServiceUid);
+ if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
+ mVoiceInteractionServiceUid);
+ }
}
@Override
protected boolean bindService(
@NonNull android.content.ServiceConnection serviceConnection) {
try {
- HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
- HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
- mVoiceInteractionServiceUid);
+ if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
+ mVoiceInteractionServiceUid);
+ }
+ String instancePrefix =
+ mIntent.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
+ ? "hotword_detector_" : "visual_query_detector_";
boolean bindResult = mContext.bindIsolatedService(
mIntent,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
- "hotword_detector_" + mInstanceNumber,
+ instancePrefix + mInstanceNumber,
mExecutor,
serviceConnection);
if (!bindResult) {
+ if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
+ mVoiceInteractionServiceUid);
+ }
+ }
+ return bindResult;
+ } catch (IllegalArgumentException e) {
+ if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
mVoiceInteractionServiceUid);
}
- return bindResult;
- } catch (IllegalArgumentException e) {
- HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
- HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
- mVoiceInteractionServiceUid);
Slog.wtf(TAG, "Can't bind to the hotword detection service!", e);
return false;
}
@@ -609,10 +717,27 @@
}
final DetectorSession session;
if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
+ if (mRemoteHotwordDetectionService == null) {
+ mRemoteHotwordDetectionService =
+ mHotwordDetectionServiceConnectionFactory.createLocked();
+ }
session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
mLock, mContext, token, callback, mVoiceInteractionServiceUid,
mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging);
+ } else if (detectorType == HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ if (mRemoteVisualQueryDetectionService == null) {
+ mRemoteVisualQueryDetectionService =
+ mVisualQueryDetectionServiceConnectionFactory.createLocked();
+ }
+ session = new VisualQueryDetectorSession(
+ mRemoteVisualQueryDetectionService, mLock, mContext, token, callback,
+ mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
+ mScheduledExecutorService, mDebugHotwordLogging);
} else {
+ if (mRemoteHotwordDetectionService == null) {
+ mRemoteHotwordDetectionService =
+ mHotwordDetectionServiceConnectionFactory.createLocked();
+ }
session = new SoftwareTrustedHotwordDetectorSession(
mRemoteHotwordDetectionService, mLock, mContext, token, callback,
mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
@@ -625,13 +750,23 @@
@SuppressWarnings("GuardedBy")
void destroyDetectorLocked(@NonNull IBinder token) {
final DetectorSession session = getDetectorSessionByTokenLocked(token);
- if (session != null) {
- session.destroyLocked();
- final int index = mDetectorSessions.indexOfValue(session);
- if (index < 0 || index > mDetectorSessions.size() - 1) {
- return;
- }
- mDetectorSessions.removeAt(index);
+ if (session == null) {
+ return;
+ }
+ session.destroyLocked();
+ final int index = mDetectorSessions.indexOfValue(session);
+ if (index < 0 || index > mDetectorSessions.size() - 1) {
+ return;
+ }
+ mDetectorSessions.removeAt(index);
+ if (session instanceof VisualQueryDetectorSession) {
+ unbindVisualQueryDetectionService();
+ }
+ // Handle case where all hotword detector sessions are destroyed with only the visual
+ // detector session left
+ if (mDetectorSessions.size() == 1
+ && mDetectorSessions.get(0) instanceof VisualQueryDetectorSession) {
+ unbindHotwordDetectionService();
}
}
@@ -672,6 +807,15 @@
}
@SuppressWarnings("GuardedBy")
+ private VisualQueryDetectorSession getVisualQueryDetectorSessionLocked() {
+ final DetectorSession session = mDetectorSessions.get(
+ HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR);
+ if (session == null || session.isDestroyed()) {
+ Slog.v(TAG, "Not found the look and talk perceiver");
+ return null;
+ }
+ return (VisualQueryDetectorSession) session;
+ }
private void runForEachDetectorSessionLocked(
@NonNull Consumer<DetectorSession> action) {
for (int i = 0; i < mDetectorSessions.size(); i++) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
new file mode 100644
index 0000000..621c3de
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 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.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.permission.Identity;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.service.voice.IDetectorSessionVisualQueryDetectionCallback;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.ISandboxedDetectionService;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.util.Slog;
+
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * A class that provides visual query detector to communicate with the {@link
+ * android.service.voice.VisualQueryDetectionService}.
+ *
+ * This class can handle the visual query detection whose detector is created by using
+ * {@link android.service.voice.VoiceInteractionService#createVisualQueryDetector(PersistableBundle
+ * ,SharedMemory, HotwordDetector.Callback)}.
+ */
+final class VisualQueryDetectorSession extends DetectorSession {
+
+ private static final String TAG = "VisualQueryDetectorSession";
+ private boolean mEgressingData;
+ private boolean mQueryStreaming;
+
+ //TODO(b/261783819): Determines actual functionalities, e.g., startRecognition etc.
+ VisualQueryDetectorSession(
+ @NonNull HotwordDetectionConnection.ServiceConnection remoteService,
+ @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
+ @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
+ Identity voiceInteractorIdentity,
+ @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+ super(remoteService, lock, context, token, callback,
+ voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
+ logging);
+ mEgressingData = false;
+ mQueryStreaming = false;
+ }
+
+ @Override
+ @SuppressWarnings("GuardedBy")
+ void informRestartProcessLocked() {
+ Slog.v(TAG, "informRestartProcessLocked");
+ mUpdateStateAfterStartFinished.set(false);
+ //TODO(b/261783819): Starts detection in VisualQueryDetectionService.
+ }
+
+ @SuppressWarnings("GuardedBy")
+ void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "startPerceivingLocked");
+ }
+
+ IDetectorSessionVisualQueryDetectionCallback internalCallback =
+ new IDetectorSessionVisualQueryDetectionCallback.Stub(){
+
+ @Override
+ public void onAttentionGained() {
+ Slog.v(TAG, "BinderCallback#onAttentionGained");
+ //TODO check to see if there is an active SysUI listener registered
+ mEgressingData = true;
+ }
+
+ @Override
+ public void onAttentionLost() {
+ Slog.v(TAG, "BinderCallback#onAttentionLost");
+ //TODO check to see if there is an active SysUI listener registered
+ mEgressingData = false;
+ }
+
+ @Override
+ public void onQueryDetected(@NonNull String partialQuery) throws RemoteException {
+ Objects.requireNonNull(partialQuery);
+ Slog.v(TAG, "BinderCallback#onQueryDetected");
+ if (!mEgressingData) {
+ Slog.v(TAG, "Query should not be egressed within the unattention state.");
+ return;
+ }
+ mQueryStreaming = true;
+ callback.onQueryDetected(partialQuery);
+ Slog.i(TAG, "Egressed from visual query detection process.");
+ }
+
+ @Override
+ public void onQueryFinished() throws RemoteException {
+ Slog.v(TAG, "BinderCallback#onQueryFinished");
+ if (!mQueryStreaming) {
+ Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
+ + " no active query being streamed.");
+ return;
+ }
+ callback.onQueryFinished();
+ mQueryStreaming = false;
+ }
+
+ @Override
+ public void onQueryRejected() throws RemoteException {
+ Slog.v(TAG, "BinderCallback#onQueryRejected");
+ if (!mQueryStreaming) {
+ Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
+ + " no active query being streamed.");
+ return;
+ }
+ callback.onQueryRejected();
+ mQueryStreaming = false;
+ }
+ };
+ mRemoteDetectionService.run(service -> service.detectWithVisualSignals(internalCallback));
+ }
+
+ @SuppressWarnings("GuardedBy")
+ void stopPerceivingLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "stopPerceivingLocked");
+ }
+ mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
+ }
+
+ @Override
+ void startListeningFromExternalSourceLocked(
+ ParcelFileDescriptor audioStream,
+ AudioFormat audioFormat,
+ @Nullable PersistableBundle options,
+ IMicrophoneHotwordDetectionVoiceInteractionCallback callback)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("HotwordDetectionService method"
+ + " should not be called from VisualQueryDetectorSession.");
+ }
+
+
+ @SuppressWarnings("GuardedBy")
+ public void dumpLocked(String prefix, PrintWriter pw) {
+ super.dumpLocked(prefix, pw);
+ pw.print(prefix);
+ }
+}
+
+
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 9a02188..38bf9c2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -72,6 +72,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionService;
@@ -330,6 +331,12 @@
@GuardedBy("this")
private boolean mTemporarilyDisabled;
+ /** The start value of showSessionId */
+ private static final int SHOW_SESSION_START_ID = 0;
+
+ @GuardedBy("this")
+ private int mShowSessionId = SHOW_SESSION_START_ID;
+
private final boolean mEnableService;
// TODO(b/226201975): remove reference once RoleService supports pre-created users
private final RoleObserver mRoleObserver;
@@ -349,6 +356,24 @@
}
}
+ int getNextShowSessionId() {
+ synchronized (this) {
+ // Reset the showSessionId to SHOW_SESSION_START_ID to avoid the value exceeds
+ // Integer.MAX_VALUE
+ if (mShowSessionId == Integer.MAX_VALUE - 1) {
+ mShowSessionId = SHOW_SESSION_START_ID;
+ }
+ mShowSessionId++;
+ return mShowSessionId;
+ }
+ }
+
+ int getShowSessionId() {
+ synchronized (this) {
+ return mShowSessionId;
+ }
+ }
+
@Override
public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
@NonNull Identity originatorIdentity, IBinder client) {
@@ -1314,6 +1339,46 @@
}
@Override
+ public void startPerceiving(
+ IVisualQueryDetectionVoiceInteractionCallback callback)
+ throws RemoteException {
+ enforceCallingPermission(Manifest.permission.RECORD_AUDIO);
+ enforceCallingPermission(Manifest.permission.CAMERA);
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService();
+
+ if (mImpl == null) {
+ Slog.w(TAG, "startPerceiving without running voice interaction service");
+ return;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.startPerceivingLocked(callback);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public void stopPerceiving() throws RemoteException {
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService();
+
+ if (mImpl == null) {
+ Slog.w(TAG, "stopPerceiving without running voice interaction service");
+ return;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.stopPerceivingLocked();
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
public void startListeningFromMic(
AudioFormat audioFormat,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback)
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 13c0f17..04c8f8f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -21,6 +21,7 @@
import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.service.voice.VoiceInteractionService.KEY_SHOW_SESSION_ID;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
@@ -58,7 +59,9 @@
import android.os.ServiceManager;
import android.os.SharedMemory;
import android.os.UserHandle;
+import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionService;
@@ -109,6 +112,7 @@
final ComponentName mSessionComponentName;
final IWindowManager mIWindowManager;
final ComponentName mHotwordDetectionComponentName;
+ final ComponentName mVisualQueryDetectionComponentName;
boolean mBound = false;
IVoiceInteractionService mService;
volatile HotwordDetectionConnection mHotwordDetectionConnection;
@@ -211,6 +215,7 @@
mInfo = null;
mSessionComponentName = null;
mHotwordDetectionComponentName = null;
+ mVisualQueryDetectionComponentName = null;
mIWindowManager = null;
mValid = false;
return;
@@ -220,6 +225,7 @@
Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError());
mSessionComponentName = null;
mHotwordDetectionComponentName = null;
+ mVisualQueryDetectionComponentName = null;
mIWindowManager = null;
mValid = false;
return;
@@ -230,6 +236,9 @@
final String hotwordDetectionServiceName = mInfo.getHotwordDetectionService();
mHotwordDetectionComponentName = hotwordDetectionServiceName != null
? new ComponentName(service.getPackageName(), hotwordDetectionServiceName) : null;
+ final String visualQueryDetectionServiceName = mInfo.getVisualQueryDetectionService();
+ mVisualQueryDetectionComponentName = visualQueryDetectionServiceName != null ? new
+ ComponentName(service.getPackageName(), visualQueryDetectionServiceName) : null;
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
IntentFilter filter = new IntentFilter();
@@ -247,13 +256,17 @@
/* direct= */ true);
}
- public boolean showSessionLocked(@NonNull Bundle args, int flags,
+ public boolean showSessionLocked(@Nullable Bundle args, int flags,
@Nullable String attributionTag,
@Nullable IVoiceInteractionSessionShowCallback showCallback,
@Nullable IBinder activityToken) {
+ final int sessionId = mServiceStub.getNextShowSessionId();
+ final Bundle newArgs = args == null ? new Bundle() : args;
+ newArgs.putInt(KEY_SHOW_SESSION_ID, sessionId);
+
try {
if (mService != null) {
- mService.prepareToShowSession(args, flags);
+ mService.prepareToShowSession(newArgs, flags);
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling prepareToShowSession", e);
@@ -267,7 +280,9 @@
if (!mActiveSession.mBound) {
try {
if (mService != null) {
- mService.showSessionFailed();
+ Bundle failedArgs = new Bundle();
+ failedArgs.putInt(KEY_SHOW_SESSION_ID, sessionId);
+ mService.showSessionFailed(failedArgs);
}
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling showSessionFailed", e);
@@ -292,7 +307,7 @@
} else {
visibleActivities = allVisibleActivities;
}
- return mActiveSession.showLocked(args, flags, attributionTag, mDisabledShowContext,
+ return mActiveSession.showLocked(newArgs, flags, attributionTag, mDisabledShowContext,
showCallback, visibleActivities);
}
@@ -591,14 +606,11 @@
}
}
- public void initAndVerifyDetectorLocked(
- @NonNull Identity voiceInteractorIdentity,
- @Nullable PersistableBundle options,
+ private void verifyDetectorForHotwordDetectionLocked(
@Nullable SharedMemory sharedMemory,
- @NonNull IBinder token,
IHotwordRecognitionStatusCallback callback,
int detectorType) {
- Slog.v(TAG, "initAndVerifyDetectorLocked");
+ Slog.v(TAG, "verifyDetectorForHotwordDetectionLocked");
int voiceInteractionServiceUid = mInfo.getServiceInfo().applicationInfo.uid;
if (mHotwordDetectionComponentName == null) {
Slog.w(TAG, "Hotword detection service name not found");
@@ -649,11 +661,70 @@
logDetectorCreateEventIfNeeded(callback, detectorType, true,
voiceInteractionServiceUid);
+ }
+
+ private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
+ Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
+
+ if (mVisualQueryDetectionComponentName == null) {
+ Slog.w(TAG, "Visual query detection service name not found");
+ throw new IllegalStateException("Visual query detection service name not found");
+ }
+ ServiceInfo visualQueryDetectionServiceInfo = getServiceInfoLocked(
+ mVisualQueryDetectionComponentName, mUser);
+ if (visualQueryDetectionServiceInfo == null) {
+ Slog.w(TAG, "Visual query detection service info not found");
+ throw new IllegalStateException("Visual query detection service name not found");
+ }
+ if (!isIsolatedProcessLocked(visualQueryDetectionServiceInfo)) {
+ Slog.w(TAG, "Visual query detection service not in isolated process");
+ throw new IllegalStateException("Visual query detection not in isolated process");
+ }
+ if (!Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE.equals(
+ visualQueryDetectionServiceInfo.permission)) {
+ Slog.w(TAG, "Visual query detection does not require permission "
+ + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+ throw new SecurityException("Visual query detection does not require permission "
+ + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+ }
+ if (mContext.getPackageManager().checkPermission(
+ Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE,
+ mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Voice interaction service should not hold permission "
+ + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+ throw new SecurityException("Voice interaction service should not hold permission "
+ + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
+ }
+ if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
+ Slog.w(TAG, "Can't set sharedMemory to be read-only");
+ throw new IllegalStateException("Can't set sharedMemory to be read-only");
+ }
+ }
+
+ public void initAndVerifyDetectorLocked(
+ @NonNull Identity voiceInteractorIdentity,
+ @Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory,
+ @NonNull IBinder token,
+ IHotwordRecognitionStatusCallback callback,
+ int detectorType) {
+
+ if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ verifyDetectorForHotwordDetectionLocked(sharedMemory, callback, detectorType);
+ } else {
+ verifyDetectorForVisualQueryDetectionLocked(sharedMemory);
+ }
+
if (mHotwordDetectionConnection == null) {
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
- mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
- detectorType);
+ mHotwordDetectionComponentName, mVisualQueryDetectionComponentName, mUser,
+ /* bindInstantServiceAllowed= */ false, detectorType);
+ } else if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+ // TODO: Logger events should be handled in session instead. Temporary adding the
+ // checking to prevent confusion so VisualQueryDetection events won't be logged if the
+ // connection is instantiated by the VisualQueryDetector.
+ mHotwordDetectionConnection.setDetectorType(detectorType);
}
mHotwordDetectionConnection.createDetectorLocked(options, sharedMemory, token, callback,
detectorType);
@@ -689,6 +760,32 @@
mHotwordDetectionConnection = null;
}
+ public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "startPerceivingLocked");
+ }
+
+ if (mHotwordDetectionConnection == null) {
+ // TODO: callback.onError();
+ return;
+ }
+
+ mHotwordDetectionConnection.startPerceivingLocked(callback);
+ }
+
+ public void stopPerceivingLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "stopPerceivingLocked");
+ }
+
+ if (mHotwordDetectionConnection == null) {
+ Slog.w(TAG, "stopPerceivingLocked() called but connection isn't established");
+ return;
+ }
+
+ mHotwordDetectionConnection.stopPerceivingLocked();
+ }
+
public void startListeningFromMicLocked(
AudioFormat audioFormat,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
diff --git a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
index 5aa58c1..ae677ca 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
@@ -24,4 +24,5 @@
void onSendSmsResult(int token, int messageRef, int status, int reason, int networkErrorCode);
void onSmsStatusReportReceived(int token, in String format, in byte[] pdu);
void onSmsReceived(int token, in String format, in byte[] pdu);
+ void onMemoryAvailableResult(int token, int status, int networkErrorCode);
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 17cb3b4..e7e0ec2 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -196,6 +196,8 @@
*
* @param token unique token generated in {@link ImsSmsDispatcher#onMemoryAvailable(void)} that
* should be used when triggering callbacks for this specific message.
+ *
+ * @hide
*/
public void onMemoryAvailable(int token) {
// Base Implementation - Should be overridden
@@ -403,6 +405,38 @@
}
/**
+ * This API is used to report the result of sending
+ * RP-SMMA to framework based on received network responses(RP-ACK,
+ * RP-ERROR or SIP error).
+ *
+ * @param token provided in {@link #onMemoryAvailable()}.
+ * @param result based on RP-ACK or RP_ERROR
+ * @param networkErrorCode the error code reported by the carrier
+ * network if sending this SMS has resulted in an error or
+ * {@link #RESULT_NO_NETWORK_ERROR} if no network error was generated. See
+ * 3GPP TS 24.011 Section 7.3.4 for valid error codes and more
+ * information.
+ *
+ * @hide
+ */
+ public final void onMemoryAvailableResult(int token, @SendStatusResult int result,
+ int networkErrorCode) throws RuntimeException {
+ IImsSmsListener listener = null;
+ synchronized (mLock) {
+ listener = mListener;
+ }
+
+ if (listener == null) {
+ throw new RuntimeException("Feature not ready.");
+ }
+ try {
+ listener.onMemoryAvailableResult(token, result, networkErrorCode);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* This method should be triggered by the IMS providers when the status report of the sent
* message is received. The platform will handle the report and notify the IMS provider of the
* result by calling {@link #acknowledgeSmsReport(int, int, int)}.
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 84781b4..707b522 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -22,6 +22,10 @@
<!-- Ensure output directory is empty at the start -->
<option name="run-command" value="rm -rf /sdcard/flicker" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
+ <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+ </target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="FlickerTests.apk"/>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index 33e9574..7d1f6cb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -46,7 +46,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
private val simpleApp = SimpleAppHelper(instrumentation)
private val testApp = ImeAppHelper(instrumentation)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt
new file mode 100644
index 0000000..c40dfae
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenImeWindowAndCloseTestCfArm(flicker: FlickerTest) :
+ OpenImeWindowAndCloseTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index c097511..5b830e5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -37,7 +37,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt
new file mode 100644
index 0000000..34bd455
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenImeWindowTestCfArm(flicker: FlickerTest) : OpenImeWindowTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index b05beba..d95af9d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -46,7 +46,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt
new file mode 100644
index 0000000..8e6d7dc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.wm.flicker.ime
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenImeWindowToOverViewTestCfArm(flicker: FlickerTest) : OpenImeWindowToOverViewTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt
new file mode 100644
index 0000000..1d3658e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.wm.flicker.ime
+
+import android.platform.test.annotations.Presubmit
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Presubmit
+open class SwitchImeWindowsFromGestureNavTestCfArm(flicker: FlickerTest) :
+ SwitchImeWindowsFromGestureNavTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 3d1342c..1baff37 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
@@ -74,11 +73,6 @@
}
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
/**
* Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at the start of the
* transition, that [ActivityOptions.SimpleActivity] becomes visible during the transition, and
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
index 4ca9d5fa..baa2750 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -21,10 +21,7 @@
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.CameraAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -42,11 +39,6 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
private val cameraApp = CameraAppHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
deleted file mode 100644
index a9f9204..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2022 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.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app after cold opening camera (with shell transitions)
- *
- * To run this test: `atest FlickerTests:OpenAppAfterCameraTest_ShellTransit`
- *
- * Notes: Some default assertions are inherited [OpenAppTransition]
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppAfterCameraTest_ShellTransit(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
- @Before
- override fun before() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- }
-
- @FlakyTest
- @Test
- override fun appLayerReplacesLauncher() {
- super.appLayerReplacesLauncher()
- }
-
- @FlakyTest
- @Test
- override fun appLayerBecomesVisible() {
- super.appLayerBecomesVisible()
- }
-
- @FlakyTest
- @Test
- override fun appWindowBecomesTopWindow() {
- super.appWindowBecomesTopWindow()
- }
-
- @FlakyTest
- @Test
- override fun appWindowBecomesVisible() {
- super.appWindowBecomesVisible()
- }
-
- @FlakyTest
- @Test
- override fun appWindowIsTopWindowAtEnd() {
- super.appWindowIsTopWindowAtEnd()
- }
-
- @FlakyTest
- @Test
- override fun appWindowReplacesLauncherAsTopWindow() {
- super.appWindowReplacesLauncherAsTopWindow()
- }
-
- @FlakyTest
- @Test
- override fun entireScreenCovered() {
- super.entireScreenCovered()
- }
-
- @FlakyTest
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() {
- super.navBarLayerIsVisibleAtStartAndEnd()
- }
-
- @FlakyTest
- @Test
- override fun navBarLayerPositionAtStartAndEnd() {
- super.navBarLayerPositionAtStartAndEnd()
- }
-
- @FlakyTest
- @Test
- override fun navBarWindowIsAlwaysVisible() {
- super.navBarWindowIsAlwaysVisible()
- }
-
- @FlakyTest
- @Test
- override fun statusBarLayerIsVisibleAtStartAndEnd() {
- super.statusBarLayerIsVisibleAtStartAndEnd()
- }
-
- @FlakyTest
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() {
- super.statusBarLayerPositionAtStartAndEnd()
- }
-
- @FlakyTest
- @Test
- override fun statusBarWindowIsAlwaysVisible() {
- super.statusBarWindowIsAlwaysVisible()
- }
-
- @FlakyTest
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() {
- super.taskBarLayerIsVisibleAtStartAndEnd()
- }
-
- @FlakyTest
- @Test
- override fun taskBarWindowIsAlwaysVisible() {
- super.taskBarWindowIsAlwaysVisible()
- }
-
- @FlakyTest
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
- }
-
- @FlakyTest
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
-
- @FlakyTest
- @Test
- override fun focusChanges() {
- super.focusChanges()
- }
-
- @FlakyTest
- @Test
- override fun appWindowAsTopWindowAtEnd() {
- super.appWindowAsTopWindowAtEnd()
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 242f457..9d86f8c8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
@@ -25,7 +24,6 @@
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -77,103 +75,6 @@
teardown { testApp.exit(wmHelper) }
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun appWindowReplacesLauncherAsTopWindow() =
- super.appWindowReplacesLauncherAsTopWindow()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028) @Test override fun focusChanges() = super.focusChanges()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun statusBarLayerIsVisibleAtStartAndEnd() =
- super.statusBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 240916028)
- @Test
- override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
-
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index a4f09c0..9fbec97 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
@@ -71,11 +70,6 @@
}
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
@Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 56d7d5e..991cd1c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -113,9 +113,6 @@
override fun navBarWindowIsAlwaysVisible() {}
/** {@inheritDoc} */
- @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
- /** {@inheritDoc} */
@Postsubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
index 6c833c4..90c18c4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -61,9 +61,9 @@
}
}
- @Postsubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+ @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
- @Postsubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
+ @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
/** {@inheritDoc} */
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index d582931..efca6ab 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.WindowInsets
@@ -112,9 +111,9 @@
teardown { testApp.exit(wmHelper) }
}
- @FlakyTest @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
+ @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
- @Postsubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
+ @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index db4baa0..2b16ef0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -87,9 +87,6 @@
}
/** {@inheritDoc} */
- @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
- /** {@inheritDoc} */
@FlakyTest
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 7a7990f..93bf099 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -79,9 +79,6 @@
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
- @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
- /** {@inheritDoc} */
@Presubmit
@Test
override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
new file mode 100644
index 0000000..41316d8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 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.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.KeyEvent
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
+import com.android.server.wm.flicker.helpers.CameraAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching camera from launcher by double pressing power button
+ *
+ * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
+ *
+ * Actions:
+ * ```
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] and wait animation to complete
+ * ```
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) :
+ OpenAppFromLauncherTransition(flicker) {
+ private val cameraApp = CameraAppHelper(instrumentation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ this.setRotation(flicker.scenario.startRotation)
+ }
+ transitions {
+ device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+ device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+ wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify()
+ }
+ teardown { RemoveAllTasksButHomeRule.removeAllTasksButHome() }
+ }
+
+ @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+ @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
+
+ @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+ @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+ @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+ @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+
+ @Postsubmit
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() =
+ super.appWindowReplacesLauncherAsTopWindow()
+
+ @Postsubmit @Test override fun focusChanges() = super.focusChanges()
+
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 31babb8..959ab3d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -20,7 +20,6 @@
import android.app.WallpaperManager
import android.content.res.Resources
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
@@ -92,7 +91,7 @@
* Checks that the [wallpaper] layer is never visible when performing task transitions. A solid
* color background should be shown instead.
*/
- @FlakyTest(bugId = 253617416)
+ @Presubmit
@Test
fun wallpaperLayerIsNeverVisible() {
flicker.assertLayers {
@@ -192,7 +191,7 @@
* Checks that we start with the LaunchNewTask activity on top and then open up the
* SimpleActivity and then go back to the LaunchNewTask activity.
*/
- @Postsubmit
+ @Presubmit
@Test
fun newTaskOpensOnTopAndThenCloses() {
flicker.assertWm {
@@ -208,11 +207,6 @@
}
}
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
companion object {
private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher {
val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
diff --git a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
index 46aad9f..bc363b0 100644
--- a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
+++ b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
@@ -31,15 +31,14 @@
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when`
-
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
import java.time.Duration
@@ -49,7 +48,6 @@
x: Float,
y: Float,
): MotionEvent{
- // One-time: send a DOWN event
val pointerCount = 1
val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
diff --git a/tests/MotionPrediction/Android.bp b/tests/MotionPrediction/Android.bp
index b9d01da..6cda8f0 100644
--- a/tests/MotionPrediction/Android.bp
+++ b/tests/MotionPrediction/Android.bp
@@ -26,6 +26,5 @@
android_app {
name: "MotionPrediction",
srcs: ["**/*.kt"],
- platform_apis: true,
- certificate: "platform",
+ sdk_version: "current",
}
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index f2234fb..21007ef 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -74,6 +74,7 @@
],
test_suites: ["general-tests"],
test_config: "MultiUserRollbackTest.xml",
+ data : [":RollbackTest"],
}
java_library_host {
diff --git a/tools/processors/immutability/Android.bp b/tools/processors/immutability/Android.bp
index 2ce785f..a7d69039 100644
--- a/tools/processors/immutability/Android.bp
+++ b/tools/processors/immutability/Android.bp
@@ -60,6 +60,9 @@
java_resources: [":ImmutabilityAnnotationJavaSource"],
test_suites: ["general-tests"],
+ test_options: {
+ unit_test: true,
+ },
javacflags: [
"--add-modules=jdk.compiler",
"--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",