summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2024-03-06 09:29:59 -0800
committerXin Li <delphij@google.com>2024-03-06 09:29:59 -0800
commit9886b47e2da1543cc4041e19bb8c8931ffb93d25 (patch)
tree0d3972f489eeb2e52a511556409b39370a154fda
parentb83530c25cf459e44c9ec829dd6229c90cfd95c1 (diff)
parentf609fe15bb4aaa9cd400180ddf11012007dfd822 (diff)
downloadservices-9886b47e2da1543cc4041e19bb8c8931ffb93d25.tar.gz
Merge Android 14 QPR2 to AOSP main
Bug: 319669529 Merged-In: Ibb33addb18eb4ae6e7336ad1122de73676a4f44e Change-Id: I46633b4e4283eac48f44d2a8fac0b1faaa5d7d66
-rw-r--r--builtInServices/Android.bp1
-rw-r--r--builtInServices/api/module-lib-current.txt70
-rw-r--r--builtInServices/prebuilts/Android.bp30
-rw-r--r--builtInServices/prebuilts/README.md2
-rw-r--r--builtInServices/prebuilts/mu_imms-prebuilt.jarbin159172 -> 0 bytes
-rw-r--r--builtInServices/src/android/content/res/CompatScaleWrapper.java48
-rw-r--r--builtInServices/src/com/android/internal/car/CarServiceHelperInterface.java21
-rw-r--r--builtInServices/src/com/android/internal/car/CarServiceHelperService.java30
-rw-r--r--builtInServices/src/com/android/internal/car/CarServiceHelperServiceUpdatable.java19
-rw-r--r--builtInServices/src/com/android/server/wm/ActivityInterceptResultWrapper.java8
-rw-r--r--builtInServices/src/com/android/server/wm/ActivityInterceptorInfoWrapper.java16
-rw-r--r--builtInServices/src/com/android/server/wm/ActivityOptionsWrapper.java13
-rw-r--r--builtInServices/src/com/android/server/wm/ActivityRecordWrapper.java12
-rw-r--r--builtInServices/src/com/android/server/wm/CalculateParams.java14
-rw-r--r--builtInServices/src/com/android/server/wm/CarActivityInterceptorInterface.java8
-rw-r--r--builtInServices/src/com/android/server/wm/CarActivityInterceptorUpdatable.java6
-rw-r--r--builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProvider.java98
-rw-r--r--builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProviderInterface.java37
-rw-r--r--builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatable.java49
-rw-r--r--builtInServices/src/com/android/server/wm/CarLaunchParamsModifierInterface.java13
-rw-r--r--builtInServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatable.java11
-rw-r--r--builtInServices/src/com/android/server/wm/LaunchParamsWrapper.java12
-rw-r--r--builtInServices/src/com/android/server/wm/RequestWrapper.java4
-rw-r--r--builtInServices/src/com/android/server/wm/TaskDisplayAreaWrapper.java5
-rw-r--r--builtInServices/src/com/android/server/wm/TaskWrapper.java15
-rw-r--r--builtInServices/src/com/android/server/wm/WindowLayoutWrapper.java3
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/AutofillController.java48
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/CarAutofillSuggestionsController.java238
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/CarDefaultImeVisibilityApplier.java178
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/CarImeVisibilityStateComputer.java738
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodBindingController.java541
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodManagerService.java6889
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodMenuController.java318
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/InputMethodManagerServiceProxy.java1133
-rw-r--r--builtInServices/src_imms/com/android/server/inputmethod/NullAutofillSuggestionsController.java43
-rw-r--r--builtInServices/tests/Android.bp1
-rw-r--r--builtInServices/tests/res/raw/CSHS_classes.txt19
-rw-r--r--builtInServices/tests/src/com/android/car/rotary/ActivityResolverTest.java299
-rw-r--r--builtInServices/tests/src/com/android/server/inputmethod/ImeSmokeTest.java151
-rw-r--r--builtInServices/tests/src/com/android/server/wm/ActivityOptionsWrapperTest.java3
-rw-r--r--updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java107
-rw-r--r--updatableServices/src/com/android/server/wm/CarActivityInterceptorUpdatableImpl.java53
-rw-r--r--updatableServices/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatableImpl.java268
-rw-r--r--updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java46
-rw-r--r--updatableServices/tests/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImplTest.java29
-rw-r--r--updatableServices/tests/src/com/android/server/wm/CarActivityInterceptorUpdatableTest.java48
-rw-r--r--updatableServices/tests/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatableTest.java120
-rw-r--r--updatableServices/tests/src/com/android/server/wm/CarLaunchParamsModifierUpdatableTest.java57
48 files changed, 1318 insertions, 10554 deletions
diff --git a/builtInServices/Android.bp b/builtInServices/Android.bp
index e4f8a92..5bcc3b0 100644
--- a/builtInServices/Android.bp
+++ b/builtInServices/Android.bp
@@ -16,7 +16,6 @@ java_sdk_library {
static_libs: [
"android.car.watchdoglib",
"android.automotive.watchdog.internal-V3-java",
- "mu_imms-prebuilt",
],
api_lint: {
enabled: true,
diff --git a/builtInServices/api/module-lib-current.txt b/builtInServices/api/module-lib-current.txt
index e625c2e..b6e285f 100644
--- a/builtInServices/api/module-lib-current.txt
+++ b/builtInServices/api/module-lib-current.txt
@@ -1,22 +1,33 @@
// Signature format: 2.0
+package android.content.res {
+
+ public final class CompatScaleWrapper {
+ ctor public CompatScaleWrapper(float, float);
+ method public float getDensityScaleFactor();
+ method public float getScaleFactor();
+ }
+
+}
+
package com.android.internal.car {
public interface CarServiceHelperInterface {
method @Nullable public android.os.UserHandle createUserEvenWhenDisallowed(@Nullable String, @NonNull String, int);
method @Nullable public java.io.File dumpServiceStacks();
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int fetchAidlVhalPid();
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getMainDisplayAssignedToUser(int);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getProcessGroup(int);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getUserAssignedToDisplay(int);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void setProcessGroup(int, int);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void setProcessProfile(int, int, @NonNull String);
+ method public int fetchAidlVhalPid();
+ method public int getMainDisplayAssignedToUser(int);
+ method public int getProcessGroup(int);
+ method public int getUserAssignedToDisplay(int);
+ method public void setProcessGroup(int, int);
+ method public void setProcessProfile(int, int, @NonNull String);
method public void setSafetyMode(boolean);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public boolean startUserInBackgroundVisibleOnDisplay(int, int);
+ method public boolean startUserInBackgroundVisibleOnDisplay(int, int);
}
public interface CarServiceHelperServiceUpdatable {
method public void dump(@NonNull java.io.PrintWriter, @Nullable String[]);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public com.android.server.wm.CarActivityInterceptorUpdatable getCarActivityInterceptorUpdatable();
+ method public com.android.server.wm.CarActivityInterceptorUpdatable getCarActivityInterceptorUpdatable();
+ method public com.android.server.wm.CarDisplayCompatScaleProviderUpdatable getCarDisplayCompatScaleProviderUpdatable();
method public com.android.server.wm.CarLaunchParamsModifierUpdatable getCarLaunchParamsModifierUpdatable();
method public void initBootUser();
method public void onFactoryReset(@NonNull java.util.function.BiConsumer<java.lang.Integer,android.os.Bundle>);
@@ -30,24 +41,24 @@ package com.android.internal.car {
package com.android.server.wm {
public final class ActivityInterceptResultWrapper {
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static com.android.server.wm.ActivityInterceptResultWrapper create(android.content.Intent, android.app.ActivityOptions);
+ method public static com.android.server.wm.ActivityInterceptResultWrapper create(android.content.Intent, android.app.ActivityOptions);
}
public final class ActivityInterceptorInfoWrapper {
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public android.content.pm.ActivityInfo getActivityInfo();
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public String getCallingPackage();
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public com.android.server.wm.ActivityOptionsWrapper getCheckedOptions();
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public android.content.Intent getIntent();
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getUserId();
+ method public android.content.pm.ActivityInfo getActivityInfo();
+ method public String getCallingPackage();
+ method public com.android.server.wm.ActivityOptionsWrapper getCheckedOptions();
+ method public android.content.Intent getIntent();
+ method public int getUserId();
}
public final class ActivityOptionsWrapper {
method public static com.android.server.wm.ActivityOptionsWrapper create(android.app.ActivityOptions);
method public com.android.server.wm.TaskDisplayAreaWrapper getLaunchTaskDisplayArea();
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getLaunchWindowingMode();
+ method public int getLaunchWindowingMode();
method public android.app.ActivityOptions getOptions();
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void setLaunchRootTask(android.os.IBinder);
- field @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final int WINDOWING_MODE_UNDEFINED = 0; // 0x0
+ method public void setLaunchRootTask(android.os.IBinder);
+ field public static final int WINDOWING_MODE_UNDEFINED = 0; // 0x0
}
public final class ActivityRecordWrapper {
@@ -75,21 +86,30 @@ package com.android.server.wm {
}
public interface CarActivityInterceptorInterface {
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getMainDisplayAssignedToUser(int);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getUserAssignedToDisplay(int);
+ method public int getMainDisplayAssignedToUser(int);
+ method public int getUserAssignedToDisplay(int);
}
public interface CarActivityInterceptorUpdatable {
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Nullable public com.android.server.wm.ActivityInterceptResultWrapper onInterceptActivityLaunch(com.android.server.wm.ActivityInterceptorInfoWrapper);
+ method @Nullable public com.android.server.wm.ActivityInterceptResultWrapper onInterceptActivityLaunch(com.android.server.wm.ActivityInterceptorInfoWrapper);
+ }
+
+ public interface CarDisplayCompatScaleProviderInterface {
+ method public int getMainDisplayAssignedToUser(int);
+ }
+
+ public interface CarDisplayCompatScaleProviderUpdatable {
+ method @Nullable public android.content.res.CompatScaleWrapper getCompatScale(@NonNull String, int);
+ method public boolean requiresDisplayCompat(@NonNull String);
}
public interface CarLaunchParamsModifierInterface {
method @Nullable public com.android.server.wm.TaskDisplayAreaWrapper findTaskDisplayArea(int, int);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getCurrentAndTargetUserIds();
+ method @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getCurrentAndTargetUserIds();
method @Nullable public com.android.server.wm.TaskDisplayAreaWrapper getDefaultTaskDisplayAreaOnDisplay(int);
method @NonNull public java.util.List<com.android.server.wm.TaskDisplayAreaWrapper> getFallbackDisplayAreasForActivity(@NonNull com.android.server.wm.ActivityRecordWrapper, @Nullable com.android.server.wm.RequestWrapper);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getMainDisplayAssignedToUser(int);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public int getUserAssignedToDisplay(int);
+ method public int getMainDisplayAssignedToUser(int);
+ method public int getUserAssignedToDisplay(int);
}
public interface CarLaunchParamsModifierUpdatable {
@@ -98,7 +118,7 @@ package com.android.server.wm {
method public void handleCurrentUserSwitching(int);
method public void handleUserStarting(int);
method public void handleUserStopped(int);
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void handleUserVisibilityChanged(int, boolean);
+ method public void handleUserVisibilityChanged(int, boolean);
}
public final class LaunchParamsWrapper {
@@ -122,7 +142,7 @@ package com.android.server.wm {
}
public final class TaskWrapper {
- method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static com.android.server.wm.TaskWrapper createFromToken(@NonNull android.os.IBinder);
+ method @Nullable public static com.android.server.wm.TaskWrapper createFromToken(@NonNull android.os.IBinder);
method public com.android.server.wm.TaskWrapper getRootTask();
method public com.android.server.wm.TaskDisplayAreaWrapper getTaskDisplayArea();
method public int getUserId();
diff --git a/builtInServices/prebuilts/Android.bp b/builtInServices/prebuilts/Android.bp
deleted file mode 100644
index e31e675..0000000
--- a/builtInServices/prebuilts/Android.bp
+++ /dev/null
@@ -1,30 +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.
-
-// Multi-User / multi-Display IMMS prebuilt jar (allows concurrent IME sessions).
-// Instructions for building this jar from the repo root:
-// 1. Run: `export BUILD_AUTOMOTIVE_IMMS_PREBUILT=true && m mu_imms`
-// 2. Copy and rename the generated jar:
-// `cp out/target/common/obj/JAVA_LIBRARIES/mu_imms_intermediates/classes.jar \
-// frameworks/opt/car/services/builtInServices/prebuilts/mu_imms-prebuilt.jar`
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_import {
- name: "mu_imms-prebuilt",
- jars: ["mu_imms-prebuilt.jar"],
- sdk_version: "current",
- min_sdk_version: "33",
-}
diff --git a/builtInServices/prebuilts/README.md b/builtInServices/prebuilts/README.md
deleted file mode 100644
index 0710ea1..0000000
--- a/builtInServices/prebuilts/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# How to build the mu_imms prebuilt jar.
-* Please check [this doc](http://go/aaos-mu-ime#building-and-deploying-mu-imms-prebuilt-jar).
diff --git a/builtInServices/prebuilts/mu_imms-prebuilt.jar b/builtInServices/prebuilts/mu_imms-prebuilt.jar
deleted file mode 100644
index 0d4c93e..0000000
--- a/builtInServices/prebuilts/mu_imms-prebuilt.jar
+++ /dev/null
Binary files differ
diff --git a/builtInServices/src/android/content/res/CompatScaleWrapper.java b/builtInServices/src/android/content/res/CompatScaleWrapper.java
new file mode 100644
index 0000000..4624efb
--- /dev/null
+++ b/builtInServices/src/android/content/res/CompatScaleWrapper.java
@@ -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.content.res;
+
+import android.annotation.SystemApi;
+
+/**
+ * Wrapper for {@link CompatibilityInfo.CompatScale} class.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class CompatScaleWrapper {
+ private final float mScaleFactor;
+ private final float mDensityScaleFactor;
+
+ public CompatScaleWrapper(float scaleFactor, float densityScaleFactor) {
+ mScaleFactor = scaleFactor;
+ mDensityScaleFactor = densityScaleFactor;
+ }
+
+ /**
+ * @return application scale factor
+ */
+ public float getScaleFactor() {
+ return mScaleFactor;
+ }
+
+ /**
+ * @return application's density scale factor
+ */
+ public float getDensityScaleFactor() {
+ return mDensityScaleFactor;
+ }
+}
diff --git a/builtInServices/src/com/android/internal/car/CarServiceHelperInterface.java b/builtInServices/src/com/android/internal/car/CarServiceHelperInterface.java
index 41a6986..68c39e1 100644
--- a/builtInServices/src/com/android/internal/car/CarServiceHelperInterface.java
+++ b/builtInServices/src/com/android/internal/car/CarServiceHelperInterface.java
@@ -17,14 +17,10 @@ package com.android.internal.car;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
-import android.car.builtin.annotation.PlatformVersion;
-import android.os.Build;
import android.os.UserHandle;
-import com.android.annotation.AddedIn;
import java.io.File;
@@ -38,56 +34,41 @@ public interface CarServiceHelperInterface {
/**
* Sets safety mode
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
void setSafetyMode(boolean safe);
/**
* Creates user even when disallowed
*/
@Nullable
- @AddedIn(PlatformVersion.TIRAMISU_0)
UserHandle createUserEvenWhenDisallowed(@Nullable String name, @NonNull String userType,
int flags);
/**
* Gets the main display assigned to the user.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
int getMainDisplayAssignedToUser(@UserIdInt int userId);
/**
* Gets the full user (i.e., not profile) assigned to the display.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
int getUserAssignedToDisplay(int displayId);
/**
* Dumps service stacks
*/
@Nullable
- @AddedIn(PlatformVersion.TIRAMISU_0)
File dumpServiceStacks();
/** Check {@link android.os.Process#setProcessGroup(int, int)}. */
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
void setProcessGroup(int pid, int group);
/** Check {@link android.os.Process#getProcessGroup(int)}. */
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
int getProcessGroup(int pid);
/** Check {@link ActivityManager#startUserInBackgroundVisibleOnDisplay(int, int)}. */
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
boolean startUserInBackgroundVisibleOnDisplay(@UserIdInt int userId, int displayId);
/** Check {@link android.os.Process#setProcessProfile(int, int, String)}. */
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
void setProcessProfile(int pid, int uid, @NonNull String profile);
/**
@@ -95,7 +76,5 @@ public interface CarServiceHelperInterface {
*
* On error, returns {@link com.android.car.internal.common.CommonConstants#INVALID_PID}.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
int fetchAidlVhalPid();
}
diff --git a/builtInServices/src/com/android/internal/car/CarServiceHelperService.java b/builtInServices/src/com/android/internal/car/CarServiceHelperService.java
index b516c1e..8acc4b1 100644
--- a/builtInServices/src/com/android/internal/car/CarServiceHelperService.java
+++ b/builtInServices/src/com/android/internal/car/CarServiceHelperService.java
@@ -57,7 +57,9 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.system.Os;
import android.system.OsConstants;
+import android.util.ArrayMap;
import android.util.Dumpable;
+import android.util.Log;
import android.util.TimeUtils;
import com.android.car.internal.common.UserHelperLite;
@@ -76,6 +78,8 @@ import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.CarActivityInterceptorInterface;
+import com.android.server.wm.CarDisplayCompatScaleProvider;
+import com.android.server.wm.CarDisplayCompatScaleProviderInterface;
import com.android.server.wm.CarLaunchParamsModifier;
import com.android.server.wm.CarLaunchParamsModifierInterface;
@@ -92,6 +96,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -111,9 +116,7 @@ public class CarServiceHelperService extends SystemService
@VisibleForTesting
static final String TAG = "CarServiceHelper";
- // TODO(b/154033860): STOPSHIP if they're still true
- private static final boolean DBG = true;
- private static final boolean VERBOSE = true;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final List<String> CAR_HIDL_INTERFACES_OF_INTEREST = Arrays.asList(
"android.hardware.automotive.audiocontrol@1.0::IAudioControl",
@@ -160,6 +163,7 @@ public class CarServiceHelperService extends SystemService
private final CarLaunchParamsModifier mCarLaunchParamsModifier;
private final CarActivityInterceptor mCarActivityInterceptor;
+ private final CarDisplayCompatScaleProvider mCarDisplayCompatScaleProvider;
private final Handler mHandler;
private final HandlerThread mHandlerThread = new HandlerThread("CarServiceHelperService");
@@ -209,17 +213,22 @@ public class CarServiceHelperService extends SystemService
mHandler = new Handler(mHandlerThread.getLooper());
mCarLaunchParamsModifier = carLaunchParamsModifier;
mCarActivityInterceptor = new CarActivityInterceptor();
+ mCarDisplayCompatScaleProvider = new CarDisplayCompatScaleProvider();
mCarWatchdogDaemonHelper = carWatchdogDaemonHelper;
try {
if (carServiceHelperServiceUpdatable == null) {
+ Map<String, Object> interfaces = new ArrayMap<>();
+ interfaces.put(CarServiceHelperInterface.class.getSimpleName(), this);
+ interfaces.put(CarLaunchParamsModifierInterface.class.getSimpleName(),
+ mCarLaunchParamsModifier.getBuiltinInterface());
+ interfaces.put(CarActivityInterceptorInterface.class.getSimpleName(),
+ mCarActivityInterceptor.getBuiltinInterface());
+ interfaces.put(CarDisplayCompatScaleProviderInterface.class.getSimpleName(),
+ mCarDisplayCompatScaleProvider.getBuiltinInterface());
mCarServiceHelperServiceUpdatable = (CarServiceHelperServiceUpdatable) Class
.forName(CSHS_UPDATABLE_CLASSNAME_STRING)
- .getConstructor(Context.class, CarServiceHelperInterface.class,
- CarLaunchParamsModifierInterface.class,
- CarActivityInterceptorInterface.class)
- .newInstance(mContext, this,
- mCarLaunchParamsModifier.getBuiltinInterface(),
- mCarActivityInterceptor.getBuiltinInterface());
+ .getConstructor(Context.class, Map.class)
+ .newInstance(mContext, interfaces);
Slogf.d(TAG, "CarServiceHelperServiceUpdatable created via reflection.");
} else {
mCarServiceHelperServiceUpdatable = carServiceHelperServiceUpdatable;
@@ -238,6 +247,8 @@ public class CarServiceHelperService extends SystemService
mCarServiceHelperServiceUpdatable.getCarLaunchParamsModifierUpdatable());
mCarActivityInterceptor.setUpdatable(mCarServiceHelperServiceUpdatable
.getCarActivityInterceptorUpdatable());
+ mCarDisplayCompatScaleProvider.setUpdatable(mCarServiceHelperServiceUpdatable
+ .getCarDisplayCompatScaleProviderUpdatable());
UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
if (umi != null) {
@@ -305,6 +316,7 @@ public class CarServiceHelperService extends SystemService
activityTaskManagerInternal.registerActivityStartInterceptor(
PRODUCT_ORDERED_ID,
mCarActivityInterceptor);
+ mCarDisplayCompatScaleProvider.init(mContext);
t.traceEnd();
}
}
diff --git a/builtInServices/src/com/android/internal/car/CarServiceHelperServiceUpdatable.java b/builtInServices/src/com/android/internal/car/CarServiceHelperServiceUpdatable.java
index 3fb8a52..e08eb6d 100644
--- a/builtInServices/src/com/android/internal/car/CarServiceHelperServiceUpdatable.java
+++ b/builtInServices/src/com/android/internal/car/CarServiceHelperServiceUpdatable.java
@@ -17,15 +17,12 @@ package com.android.internal.car;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
-import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
-import com.android.annotation.AddedIn;
import com.android.server.wm.CarActivityInterceptorUpdatable;
+import com.android.server.wm.CarDisplayCompatScaleProviderUpdatable;
import com.android.server.wm.CarLaunchParamsModifierUpdatable;
import java.io.PrintWriter;
@@ -40,29 +37,25 @@ import java.util.function.BiConsumer;
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public interface CarServiceHelperServiceUpdatable {
- @AddedIn(PlatformVersion.TIRAMISU_0)
void onUserRemoved(@NonNull UserHandle userHandle);
- @AddedIn(PlatformVersion.TIRAMISU_0)
void onStart();
- @AddedIn(PlatformVersion.TIRAMISU_0)
void dump(@NonNull PrintWriter pw, @Nullable String[] args);
- @AddedIn(PlatformVersion.TIRAMISU_0)
void sendUserLifecycleEvent(int eventType, @Nullable UserHandle userFrom,
@NonNull UserHandle userTo);
- @AddedIn(PlatformVersion.TIRAMISU_0)
void onFactoryReset(@NonNull BiConsumer<Integer, Bundle> processFactoryReset);
- @AddedIn(PlatformVersion.TIRAMISU_0)
void initBootUser();
- @AddedIn(PlatformVersion.TIRAMISU_0)
CarLaunchParamsModifierUpdatable getCarLaunchParamsModifierUpdatable();
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
CarActivityInterceptorUpdatable getCarActivityInterceptorUpdatable();
+
+ /**
+ * @return updatable implemtantion of CarDisplayCompatScaleProvider
+ */
+ CarDisplayCompatScaleProviderUpdatable getCarDisplayCompatScaleProviderUpdatable();
}
diff --git a/builtInServices/src/com/android/server/wm/ActivityInterceptResultWrapper.java b/builtInServices/src/com/android/server/wm/ActivityInterceptResultWrapper.java
index 5c9dea5..5812a38 100644
--- a/builtInServices/src/com/android/server/wm/ActivityInterceptResultWrapper.java
+++ b/builtInServices/src/com/android/server/wm/ActivityInterceptResultWrapper.java
@@ -16,14 +16,10 @@
package com.android.server.wm;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
import android.app.ActivityOptions;
-import android.car.builtin.annotation.PlatformVersion;
import android.content.Intent;
-import android.os.Build;
-import com.android.annotation.AddedIn;
/**
* A wrapper over {@link com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult}.
@@ -42,8 +38,6 @@ public final class ActivityInterceptResultWrapper {
/**
* Creates an instance of {@link ActivityInterceptResultWrapper}.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static ActivityInterceptResultWrapper create(Intent intent,
ActivityOptions activityOptions) {
return new ActivityInterceptResultWrapper(
@@ -53,8 +47,6 @@ public final class ActivityInterceptResultWrapper {
/**
* @hide
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public ActivityInterceptorCallback.ActivityInterceptResult getInterceptResult() {
return mActivityInterceptorInfo;
}
diff --git a/builtInServices/src/com/android/server/wm/ActivityInterceptorInfoWrapper.java b/builtInServices/src/com/android/server/wm/ActivityInterceptorInfoWrapper.java
index eb2ebf1..b7a7bd9 100644
--- a/builtInServices/src/com/android/server/wm/ActivityInterceptorInfoWrapper.java
+++ b/builtInServices/src/com/android/server/wm/ActivityInterceptorInfoWrapper.java
@@ -16,14 +16,10 @@
package com.android.server.wm;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.os.Build;
-import com.android.annotation.AddedIn;
/**
* A wrapper over {@link com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo}.
@@ -45,40 +41,28 @@ public final class ActivityInterceptorInfoWrapper {
* @param interceptorInfo the original interceptorInfo that needs to be wrapped.
* @hide
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static ActivityInterceptorInfoWrapper create(
ActivityInterceptorCallback.ActivityInterceptorInfo interceptorInfo) {
return new ActivityInterceptorInfoWrapper(interceptorInfo);
}
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public Intent getIntent() {
return mActivityInterceptorInfo.getIntent();
}
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public ActivityInfo getActivityInfo() {
return mActivityInterceptorInfo.getActivityInfo();
}
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public ActivityOptionsWrapper getCheckedOptions() {
return ActivityOptionsWrapper.create(mActivityInterceptorInfo.getCheckedOptions());
}
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public String getCallingPackage() {
return mActivityInterceptorInfo.getCallingPackage();
}
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public int getUserId() {
return mActivityInterceptorInfo.getUserId();
}
diff --git a/builtInServices/src/com/android/server/wm/ActivityOptionsWrapper.java b/builtInServices/src/com/android/server/wm/ActivityOptionsWrapper.java
index 1c65b68..d7d9d96 100644
--- a/builtInServices/src/com/android/server/wm/ActivityOptionsWrapper.java
+++ b/builtInServices/src/com/android/server/wm/ActivityOptionsWrapper.java
@@ -16,15 +16,11 @@
package com.android.server.wm;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
import android.app.ActivityOptions;
-import android.car.builtin.annotation.PlatformVersion;
-import android.os.Build;
import android.os.IBinder;
import android.window.WindowContainerToken;
-import com.android.annotation.AddedIn;
/**
* Wrapper of {@link ActivityOptions}.
@@ -35,8 +31,6 @@ public final class ActivityOptionsWrapper {
private final ActivityOptions mOptions;
/** See {@link android.app.WindowConfiguration#WINDOWING_MODE_UNDEFINED}. */
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static final int WINDOWING_MODE_UNDEFINED = 0;
private ActivityOptionsWrapper(ActivityOptions options) {
@@ -46,7 +40,6 @@ public final class ActivityOptionsWrapper {
/**
* Creates a new instance of {@link ActivityOptionsWrapper}.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static ActivityOptionsWrapper create(ActivityOptions options) {
if (options == null) return null;
return new ActivityOptionsWrapper(options);
@@ -56,7 +49,6 @@ public final class ActivityOptionsWrapper {
* Gets the underlying {@link ActivityOptions} that is wrapped by this instance.
*/
// Exposed the original object in order to allow to use the public accessors.
- @AddedIn(PlatformVersion.TIRAMISU_0)
public ActivityOptions getOptions() {
return mOptions;
}
@@ -64,8 +56,6 @@ public final class ActivityOptionsWrapper {
/**
* Gets the windowing mode to launch the Activity into
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public int getLaunchWindowingMode() {
return mOptions.getLaunchWindowingMode();
}
@@ -73,7 +63,6 @@ public final class ActivityOptionsWrapper {
/**
* Gets {@link TaskDisplayAreaWrapper} to launch the Activity into
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public TaskDisplayAreaWrapper getLaunchTaskDisplayArea() {
WindowContainerToken daToken = mOptions.getLaunchTaskDisplayArea();
if (daToken == null) return null;
@@ -93,8 +82,6 @@ public final class ActivityOptionsWrapper {
* Sets the given {@code windowContainerToken} as the launch root task. See
* {@link ActivityOptions#setLaunchRootTask(WindowContainerToken)} for more info.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
public void setLaunchRootTask(IBinder windowContainerToken) {
WindowContainerToken launchRootTaskToken = WindowContainer.fromBinder(windowContainerToken)
.mRemoteToken.toWindowContainerToken();
diff --git a/builtInServices/src/com/android/server/wm/ActivityRecordWrapper.java b/builtInServices/src/com/android/server/wm/ActivityRecordWrapper.java
index 9bf6b30..68f1c5f 100644
--- a/builtInServices/src/com/android/server/wm/ActivityRecordWrapper.java
+++ b/builtInServices/src/com/android/server/wm/ActivityRecordWrapper.java
@@ -18,11 +18,9 @@ package com.android.server.wm;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
-import com.android.annotation.AddedIn;
/**
* Wrapper of {@link ActivityRecord}.
@@ -37,14 +35,12 @@ public final class ActivityRecordWrapper {
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static ActivityRecordWrapper create(@Nullable ActivityRecord activityRecord) {
if (activityRecord == null) return null;
return new ActivityRecordWrapper(activityRecord);
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public ActivityRecord getActivityRecord() {
return mActivityRecord;
}
@@ -52,7 +48,6 @@ public final class ActivityRecordWrapper {
/**
* Gets which user this Activity is running for.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public int getUserId() {
return mActivityRecord.mUserId;
}
@@ -60,7 +55,6 @@ public final class ActivityRecordWrapper {
/**
* Gets the actual {@link ComponentName} of this Activity.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public ComponentName getComponentName() {
if (mActivityRecord.info == null) return null;
return mActivityRecord.info.getComponentName();
@@ -69,7 +63,6 @@ public final class ActivityRecordWrapper {
/**
* Gets the {@link TaskDisplayAreaWrapper} where this is located.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public TaskDisplayAreaWrapper getDisplayArea() {
return TaskDisplayAreaWrapper.create(mActivityRecord.getDisplayArea());
}
@@ -77,7 +70,6 @@ public final class ActivityRecordWrapper {
/**
* Returns whether this Activity is not displayed.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public boolean isNoDisplay() {
return mActivityRecord.noDisplay;
}
@@ -85,7 +77,6 @@ public final class ActivityRecordWrapper {
/**
* Gets {@link TaskDisplayAreaWrapper} where the handover Activity is supposed to launch
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public TaskDisplayAreaWrapper getHandoverTaskDisplayArea() {
return TaskDisplayAreaWrapper.create(mActivityRecord.mHandoverTaskDisplayArea);
}
@@ -93,7 +84,6 @@ public final class ActivityRecordWrapper {
/**
* Gets {@code displayId} where the handover Activity is supposed to launch
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public int getHandoverLaunchDisplayId() {
return mActivityRecord.mHandoverLaunchDisplayId;
}
@@ -101,7 +91,6 @@ public final class ActivityRecordWrapper {
/**
* Returns whether this Activity allows to be embedded in the other Activity.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public boolean allowingEmbedded() {
if (mActivityRecord.info == null) return false;
return (mActivityRecord.info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
@@ -110,7 +99,6 @@ public final class ActivityRecordWrapper {
/**
* Returns whether the display where this Activity is located is trusted.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public boolean isDisplayTrusted() {
return mActivityRecord.getDisplayContent().isTrusted();
}
diff --git a/builtInServices/src/com/android/server/wm/CalculateParams.java b/builtInServices/src/com/android/server/wm/CalculateParams.java
index 0973882..dfb1598 100644
--- a/builtInServices/src/com/android/server/wm/CalculateParams.java
+++ b/builtInServices/src/com/android/server/wm/CalculateParams.java
@@ -18,11 +18,8 @@ package com.android.server.wm;
import android.annotation.SystemApi;
import android.app.ActivityOptions;
-import android.car.builtin.annotation.PlatformVersion;
import android.content.pm.ActivityInfo;
-import android.view.WindowLayout;
-import com.android.annotation.AddedIn;
/**
* Wrapper of the parameters of {@code LaunchParamsController.LaunchParamsModifier.onCalculate()}
@@ -44,7 +41,6 @@ public final class CalculateParams {
private CalculateParams() {}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static CalculateParams create(Task task, ActivityInfo.WindowLayout layout,
ActivityRecord actvity, ActivityRecord source,
ActivityOptions options, ActivityStarter.Request request, int phase,
@@ -68,7 +64,6 @@ public final class CalculateParams {
/**
* Gets the {@link TaskWrapper} currently being positioned.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public TaskWrapper getTask() {
return mTask;
}
@@ -76,7 +71,6 @@ public final class CalculateParams {
/**
* Gets the specified {@link WindowLayoutWrapper}.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public WindowLayoutWrapper getWindowLayout() {
return mLayout;
}
@@ -84,7 +78,6 @@ public final class CalculateParams {
/**
* Gets the {@link ActivityRecordWrapper} currently being positioned.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public ActivityRecordWrapper getActivity() {
return mActivity;
}
@@ -92,7 +85,6 @@ public final class CalculateParams {
/**
* Gets the {@link ActivityRecordWrapper} from which activity was started from.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public ActivityRecordWrapper getSource() {
return mSource;
}
@@ -100,7 +92,6 @@ public final class CalculateParams {
/**
* Gets the {@link ActivityOptionsWrapper} specified for the activity.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public ActivityOptionsWrapper getOptions() {
return mOptions;
}
@@ -108,7 +99,6 @@ public final class CalculateParams {
/**
* Gets the optional {@link RequestWrapper} from the activity starter.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public RequestWrapper getRequest() {
return mRequest;
}
@@ -117,7 +107,6 @@ public final class CalculateParams {
* Gets the {@link LaunchParamsController.LaunchParamsModifier.Phase} that the resolution should
* finish.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public int getPhase() {
return mPhase;
}
@@ -125,7 +114,6 @@ public final class CalculateParams {
/**
* Gets the current {@link LaunchParamsWrapper}.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public LaunchParamsWrapper getCurrentParams() {
return mCurrentParams;
}
@@ -133,7 +121,6 @@ public final class CalculateParams {
/**
* Gets the resulting {@link LaunchParamsWrapper}.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public LaunchParamsWrapper getOutParams() {
return mOutParams;
}
@@ -141,7 +128,6 @@ public final class CalculateParams {
/**
* Returns whether the current system supports the multiple display.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public boolean supportsMultiDisplay() {
return mSupportsMultiDisplay;
}
diff --git a/builtInServices/src/com/android/server/wm/CarActivityInterceptorInterface.java b/builtInServices/src/com/android/server/wm/CarActivityInterceptorInterface.java
index fed022e..bf8ec09 100644
--- a/builtInServices/src/com/android/server/wm/CarActivityInterceptorInterface.java
+++ b/builtInServices/src/com/android/server/wm/CarActivityInterceptorInterface.java
@@ -16,13 +16,9 @@
package com.android.server.wm;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
-import android.car.builtin.annotation.PlatformVersion;
-import android.os.Build;
-import com.android.annotation.AddedIn;
/**
* Interface implemented by {@link com.android.internal.car.CarActivityInterceptor} and used by
@@ -42,8 +38,6 @@ public interface CarActivityInterceptorInterface {
* See {@link com.android.server.pm.UserManagerInternal#getUserAssignedToDisplay(int)} for
* the detail.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
@UserIdInt int getUserAssignedToDisplay(int displayId);
/**
@@ -52,7 +46,5 @@ public interface CarActivityInterceptorInterface {
* See {@link com.android.server.pm.UserManagerInternal#getMainDisplayAssignedToUser(int)} for
* the detail.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
int getMainDisplayAssignedToUser(@UserIdInt int userId);
}
diff --git a/builtInServices/src/com/android/server/wm/CarActivityInterceptorUpdatable.java b/builtInServices/src/com/android/server/wm/CarActivityInterceptorUpdatable.java
index 2e60a55..8a946b6 100644
--- a/builtInServices/src/com/android/server/wm/CarActivityInterceptorUpdatable.java
+++ b/builtInServices/src/com/android/server/wm/CarActivityInterceptorUpdatable.java
@@ -17,12 +17,8 @@
package com.android.server.wm;
import android.annotation.Nullable;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
-import android.os.Build;
-import com.android.annotation.AddedIn;
/**
* Updatable interface of CarActivityInterceptor.
@@ -39,8 +35,6 @@ public interface CarActivityInterceptorUpdatable {
* {@code null} is returned when no modification is required on intent or activity
* options.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
@Nullable
ActivityInterceptResultWrapper onInterceptActivityLaunch(
ActivityInterceptorInfoWrapper info);
diff --git a/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProvider.java b/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProvider.java
new file mode 100644
index 0000000..934fb9b
--- /dev/null
+++ b/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProvider.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.car.builtin.util.Slogf;
+import android.car.feature.Flags;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.CompatScaleWrapper;
+import android.content.res.CompatibilityInfo.CompatScale;
+import android.os.UserHandle;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+/**
+ * Automotive implementation of {@link CompatScaleProvider}
+ * This class is responsible for providing different scaling factor for some automotive specific
+ * packages.
+ *
+ * @hide
+ */
+public final class CarDisplayCompatScaleProvider implements CompatScaleProvider {
+ private static final String TAG = CarDisplayCompatScaleProvider.class.getSimpleName();
+ public static final String AUTOENHANCE_SYSTEM_FEATURE = "android.car.displaycompatibility";
+
+ private CarDisplayCompatScaleProviderUpdatable mCarCompatScaleProviderUpdatable;
+ private ActivityTaskManagerService mAtms;
+
+ /**
+ * Registers {@link CarDisplayCompatScaleProvider} with {@link ActivityTaskManagerService}
+ */
+ public void init(Context context) {
+ if (!Flags.displayCompatibility()) {
+ Slogf.i(TAG, Flags.FLAG_DISPLAY_COMPATIBILITY + " is not enabled");
+ return;
+ }
+ PackageManager packageManager = context.getPackageManager();
+ if (packageManager.hasSystemFeature(AUTOENHANCE_SYSTEM_FEATURE)) {
+ mAtms = (ActivityTaskManagerService) ActivityTaskManager.getService();
+ mAtms.registerCompatScaleProvider(COMPAT_SCALE_MODE_PRODUCT, this);
+ Slogf.i(TAG, "registered Car service as a CompatScaleProvider.");
+ }
+ }
+
+ /**
+ * Sets the given {@link CarActivityInterceptorUpdatable} which this internal class will
+ * communicate with.
+ */
+ public void setUpdatable(
+ CarDisplayCompatScaleProviderUpdatable carCompatScaleProviderUpdatable) {
+ mCarCompatScaleProviderUpdatable = carCompatScaleProviderUpdatable;
+ }
+
+ @Nullable
+ @Override
+ public CompatScale getCompatScale(@NonNull String packageName, int uid) {
+ if (mCarCompatScaleProviderUpdatable == null) {
+ Slogf.w(TAG, "mCarCompatScaleProviderUpdatable not set");
+ return null;
+ }
+ CompatScaleWrapper wrapper = mCarCompatScaleProviderUpdatable
+ .getCompatScale(packageName, UserHandle.getUserId(uid));
+ return wrapper == null ? null : new CompatScale(wrapper.getScaleFactor(),
+ wrapper.getDensityScaleFactor());
+ }
+
+ /**
+ * @return an interface that exposes mainly APIs that are not available on client side.
+ */
+ public CarDisplayCompatScaleProviderInterface getBuiltinInterface() {
+ return new CarDisplayCompatScaleProviderInterface() {
+ @Override
+ public int getMainDisplayAssignedToUser(int userId) {
+ return LocalServices.getService(UserManagerInternal.class)
+ .getMainDisplayAssignedToUser(userId);
+ }
+ };
+ }
+}
diff --git a/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProviderInterface.java b/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProviderInterface.java
new file mode 100644
index 0000000..cdccc2a
--- /dev/null
+++ b/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProviderInterface.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
+
+/**
+ * Interface implemented by {@link com.android.server.wm.CarDisplayCompatScaleProvider} and
+ * used by {@link CarDisplayCompatScaleProviderUpdatable}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public interface CarDisplayCompatScaleProviderInterface {
+ /**
+ * Returns the main display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
+ * user is not assigned to any main display.
+ * See {@link com.android.server.pm.UserManagerInternal#getMainDisplayAssignedToUser(int)} for
+ * the detail.
+ */
+ int getMainDisplayAssignedToUser(@UserIdInt int userId);
+}
diff --git a/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatable.java b/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatable.java
new file mode 100644
index 0000000..0b6eef4
--- /dev/null
+++ b/builtInServices/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatable.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
+import android.content.res.CompatScaleWrapper;
+
+/**
+ * Updatable interface of {@link CarDisplayCompatScaleProvider}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public interface CarDisplayCompatScaleProviderUpdatable {
+
+ /**
+ * This method is used to scale the height/width/density of a given package.
+ * This is called before initialization of the application context therefore the app will have
+ * no idea about the real width/height/density of the device.
+ *
+ * @param packageName package name of the running application
+ * @param userId user id that is running the application
+ * @return scaling factor for the given package name. return null if package was not handled.
+ */
+ @Nullable
+ CompatScaleWrapper getCompatScale(@NonNull String packageName, @UserIdInt int userId);
+
+ /**
+ * @param packageName package name of the running application
+ * @return true if package requires launching in automotive compatibility mode
+ */
+ boolean requiresDisplayCompat(@NonNull String packageName);
+}
diff --git a/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierInterface.java b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierInterface.java
index ffe3b1f..e7a91a4 100644
--- a/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierInterface.java
+++ b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierInterface.java
@@ -18,14 +18,10 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
-import android.car.builtin.annotation.PlatformVersion;
-import android.os.Build;
import android.util.Pair;
-import com.android.annotation.AddedIn;
import java.util.List;
@@ -44,19 +40,16 @@ public interface CarLaunchParamsModifierInterface {
* Returns {@link TaskDisplayAreaWrapper} of the given {@code featureId} in the given
* {@code displayId}.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
@Nullable TaskDisplayAreaWrapper findTaskDisplayArea(int displayId, int featureId);
/**
* Returns the default {@link TaskDisplayAreaWrapper} of the given {@code displayId}.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
@Nullable TaskDisplayAreaWrapper getDefaultTaskDisplayAreaOnDisplay(int displayId);
/**
* Returns the list of fallback {@link TaskDisplayAreaWrapper} from the source of the request.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
@NonNull List<TaskDisplayAreaWrapper> getFallbackDisplayAreasForActivity(
@NonNull ActivityRecordWrapper activityRecord, @Nullable RequestWrapper request);
@@ -65,8 +58,6 @@ public interface CarLaunchParamsModifierInterface {
* The target userId is the user to switch during switching the driver,
* or {@link android.os.UserHandle.USER_NULL}.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
@NonNull Pair<Integer, Integer> getCurrentAndTargetUserIds();
/**
@@ -76,8 +67,6 @@ public interface CarLaunchParamsModifierInterface {
* See {@link com.android.server.pm.UserManagerInternal#getUserAssignedToDisplay(int)} for
* the detail.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
@UserIdInt int getUserAssignedToDisplay(int displayId);
/**
@@ -86,7 +75,5 @@ public interface CarLaunchParamsModifierInterface {
* See {@link com.android.server.pm.UserManagerInternal#getMainDisplayAssignedToUser(int)} for
* the detail.
*/
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
int getMainDisplayAssignedToUser(@UserIdInt int userId);
}
diff --git a/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatable.java b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatable.java
index a381e34..cfdfda3 100644
--- a/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatable.java
+++ b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatable.java
@@ -16,14 +16,10 @@
package com.android.server.wm;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
-import android.car.builtin.annotation.PlatformVersion;
import android.hardware.display.DisplayManager;
-import android.os.Build;
-import com.android.annotation.AddedIn;
/**
* Updatable interface of CarLaunchParamsModifier.
@@ -33,30 +29,23 @@ import com.android.annotation.AddedIn;
public interface CarLaunchParamsModifierUpdatable {
/** Returns {@link DisplayManager.DisplayListener} of CarLaunchParamsModifierUpdatable. */
- @AddedIn(PlatformVersion.TIRAMISU_0)
DisplayManager.DisplayListener getDisplayListener();
/** Notifies user switching. */
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
void handleUserVisibilityChanged(@UserIdInt int userId, boolean visible);
/** Notifies user switching. */
- @AddedIn(PlatformVersion.TIRAMISU_0)
void handleCurrentUserSwitching(@UserIdInt int newUserId);
/** Notifies user starting. */
- @AddedIn(PlatformVersion.TIRAMISU_0)
void handleUserStarting(@UserIdInt int startingUser);
/** Notifies user stopped. */
- @AddedIn(PlatformVersion.TIRAMISU_0)
void handleUserStopped(@UserIdInt int stoppedUser);
/**
* Calculates {@code outParams} based on the given arguments.
* See {@code LaunchParamsController.LaunchParamsModifier.onCalculate()} for the detail.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
int calculate(CalculateParams params);
}
diff --git a/builtInServices/src/com/android/server/wm/LaunchParamsWrapper.java b/builtInServices/src/com/android/server/wm/LaunchParamsWrapper.java
index 5f1f696..a9b61ac 100644
--- a/builtInServices/src/com/android/server/wm/LaunchParamsWrapper.java
+++ b/builtInServices/src/com/android/server/wm/LaunchParamsWrapper.java
@@ -18,10 +18,8 @@ package com.android.server.wm;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
import android.graphics.Rect;
-import com.android.annotation.AddedIn;
/**
* Wrapper of {@link com.android.server.wm.LaunchParamsController.LaunchParams}.
@@ -30,19 +28,16 @@ import com.android.annotation.AddedIn;
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class LaunchParamsWrapper {
/** Returned when the modifier does not want to influence the bounds calculation */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static int RESULT_SKIP = LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
/**
* Returned when the modifier has changed the bounds and would like its results to be the
* final bounds applied.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static int RESULT_DONE = LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
/**
* Returned when the modifier has changed the bounds but is okay with other modifiers
* influencing the bounds.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static int RESULT_CONTINUE = LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
private final LaunchParamsController.LaunchParams mLaunchParams;
@@ -52,7 +47,6 @@ public final class LaunchParamsWrapper {
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static LaunchParamsWrapper create(
@Nullable LaunchParamsController.LaunchParams launchParams) {
if (launchParams == null) return null;
@@ -62,7 +56,6 @@ public final class LaunchParamsWrapper {
/**
* Gets the {@link TaskDisplayAreaWrapper} the {@link Task} would prefer to be on.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public TaskDisplayAreaWrapper getPreferredTaskDisplayArea() {
return TaskDisplayAreaWrapper.create(mLaunchParams.mPreferredTaskDisplayArea);
}
@@ -70,7 +63,6 @@ public final class LaunchParamsWrapper {
/**
* Sets the {@link TaskDisplayAreaWrapper} the {@link Task} would prefer to be on.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public void setPreferredTaskDisplayArea(TaskDisplayAreaWrapper tda) {
mLaunchParams.mPreferredTaskDisplayArea = tda.getTaskDisplayArea();
}
@@ -78,7 +70,6 @@ public final class LaunchParamsWrapper {
/**
* Gets the windowing mode to be in.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public int getWindowingMode() {
return mLaunchParams.mWindowingMode;
}
@@ -86,7 +77,6 @@ public final class LaunchParamsWrapper {
/**
* Sets the windowing mode to be in.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public void setWindowingMode(int windowingMode) {
mLaunchParams.mWindowingMode = windowingMode;
}
@@ -94,7 +84,6 @@ public final class LaunchParamsWrapper {
/**
* Gets the bounds within the parent container.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public Rect getBounds() {
return mLaunchParams.mBounds;
}
@@ -102,7 +91,6 @@ public final class LaunchParamsWrapper {
/**
* Sets the bounds within the parent container.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public void setBounds(Rect bounds) {
mLaunchParams.mBounds.set(bounds);
}
diff --git a/builtInServices/src/com/android/server/wm/RequestWrapper.java b/builtInServices/src/com/android/server/wm/RequestWrapper.java
index c5df2d9..170311b 100644
--- a/builtInServices/src/com/android/server/wm/RequestWrapper.java
+++ b/builtInServices/src/com/android/server/wm/RequestWrapper.java
@@ -18,9 +18,7 @@ package com.android.server.wm;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
-import com.android.annotation.AddedIn;
/**
* Wrapper of {@link com.android.server.wm.ActivityStarter.Request}.
@@ -35,14 +33,12 @@ public final class RequestWrapper {
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static RequestWrapper create(@Nullable ActivityStarter.Request request) {
if (request == null) return null;
return new RequestWrapper(request);
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public ActivityStarter.Request getRequest() {
return mRequest;
}
diff --git a/builtInServices/src/com/android/server/wm/TaskDisplayAreaWrapper.java b/builtInServices/src/com/android/server/wm/TaskDisplayAreaWrapper.java
index 5617ad3..3db38dc 100644
--- a/builtInServices/src/com/android/server/wm/TaskDisplayAreaWrapper.java
+++ b/builtInServices/src/com/android/server/wm/TaskDisplayAreaWrapper.java
@@ -18,10 +18,8 @@ package com.android.server.wm;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
import android.view.Display;
-import com.android.annotation.AddedIn;
/**
* Wrapper of {@link TaskDisplayArea}.
@@ -36,14 +34,12 @@ public final class TaskDisplayAreaWrapper {
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static TaskDisplayAreaWrapper create(@Nullable TaskDisplayArea taskDisplayArea) {
if (taskDisplayArea == null) return null;
return new TaskDisplayAreaWrapper(taskDisplayArea);
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public TaskDisplayArea getTaskDisplayArea() {
return mTaskDisplayArea;
}
@@ -51,7 +47,6 @@ public final class TaskDisplayAreaWrapper {
/**
* Gets the display this {@link TaskDisplayAreaWrapper} is on.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public Display getDisplay() {
return mTaskDisplayArea.mDisplayContent.getDisplay();
}
diff --git a/builtInServices/src/com/android/server/wm/TaskWrapper.java b/builtInServices/src/com/android/server/wm/TaskWrapper.java
index 089a6eb..0d4125c 100644
--- a/builtInServices/src/com/android/server/wm/TaskWrapper.java
+++ b/builtInServices/src/com/android/server/wm/TaskWrapper.java
@@ -16,16 +16,11 @@
package com.android.server.wm;
-import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
import android.os.IBinder;
-import com.android.annotation.AddedIn;
/**
* Wrapper of {@link Task}.
@@ -40,23 +35,21 @@ public final class TaskWrapper {
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
+ @Nullable
public static TaskWrapper create(@Nullable Task task) {
if (task == null) return null;
return new TaskWrapper(task);
}
/** Creates an instance of {@link TaskWrapper} based on the task's remote {@code token}. */
- @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0)
- @RequiresApi(UPSIDE_DOWN_CAKE)
+ @Nullable
public static TaskWrapper createFromToken(@NonNull IBinder token) {
- return new TaskWrapper((Task) WindowContainer.fromBinder(token));
+ return create((Task) WindowContainer.fromBinder(token));
}
/**
* Gets the {@code userId} of this {@link Task} is created for
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public int getUserId() {
return mTask.mUserId;
}
@@ -64,7 +57,6 @@ public final class TaskWrapper {
/**
* Gets the root {@link TaskWrapper} of the this.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public TaskWrapper getRootTask() {
return create(mTask.getRootTask());
}
@@ -72,7 +64,6 @@ public final class TaskWrapper {
/**
* Gets the {@link TaskDisplayAreaWrapper} this {@link Task} is on.
*/
- @AddedIn(PlatformVersion.TIRAMISU_0)
public TaskDisplayAreaWrapper getTaskDisplayArea() {
return TaskDisplayAreaWrapper.create(mTask.getTaskDisplayArea());
}
diff --git a/builtInServices/src/com/android/server/wm/WindowLayoutWrapper.java b/builtInServices/src/com/android/server/wm/WindowLayoutWrapper.java
index 5e8a31e..21bbc67 100644
--- a/builtInServices/src/com/android/server/wm/WindowLayoutWrapper.java
+++ b/builtInServices/src/com/android/server/wm/WindowLayoutWrapper.java
@@ -18,10 +18,8 @@ package com.android.server.wm;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.car.builtin.annotation.PlatformVersion;
import android.content.pm.ActivityInfo;
-import com.android.annotation.AddedIn;
/**
* Wrapper of {@link android.content.pm.ActivityInfo.WindowLayout}.
@@ -36,7 +34,6 @@ public final class WindowLayoutWrapper {
}
/** @hide */
- @AddedIn(PlatformVersion.TIRAMISU_0)
public static WindowLayoutWrapper create(@Nullable ActivityInfo.WindowLayout layout) {
if (layout == null) return null;
return new WindowLayoutWrapper(layout);
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/AutofillController.java b/builtInServices/src_imms/com/android/server/inputmethod/AutofillController.java
deleted file mode 100644
index 83ecd3e..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/AutofillController.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.inputmethod;
-
-import android.annotation.UserIdInt;
-
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
-import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
-
-/**
- * Interface for Android Auto autofill controllers.
- */
-public interface AutofillController {
-
- /**
- * Fill the autofill suggestion request passed as argument. Starts an autofill Session with the
- * current IME.
- */
- void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
- InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback,
- boolean touchExplorationEnabled);
-
- /**
- * Send the autofill suggestions request. The callback passed in
- * {@link #onCreateInlineSuggestionsRequest} will be invoked with retrieved suggestions.
- */
- void performOnCreateInlineSuggestionsRequest();
-
- /**
- * Closes the autofill session with IME.
- */
- void invalidateAutofillSession();
-}
-
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/CarAutofillSuggestionsController.java b/builtInServices/src_imms/com/android/server/inputmethod/CarAutofillSuggestionsController.java
deleted file mode 100644
index 8a9bbd0..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/CarAutofillSuggestionsController.java
+++ /dev/null
@@ -1,238 +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.inputmethod;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.inputmethod.InlineSuggestionsRequest;
-import android.view.inputmethod.InputMethodInfo;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
-import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback;
-import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
-
-/**
- * A controller managing autofill suggestion requests.
- */
-final class CarAutofillSuggestionsController implements AutofillController {
- private static final boolean DEBUG = false;
- private static final String TAG = CarAutofillSuggestionsController.class.getSimpleName();
-
- @NonNull private final CarInputMethodManagerService mService;
- @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap;
- @NonNull private final InputMethodUtils.InputMethodSettings mSettings;
-
- private static final class CreateInlineSuggestionsRequest {
- @NonNull final InlineSuggestionsRequestInfo mRequestInfo;
- @NonNull final IInlineSuggestionsRequestCallback mCallback;
- @NonNull final String mPackageName;
-
- CreateInlineSuggestionsRequest(
- @NonNull InlineSuggestionsRequestInfo requestInfo,
- @NonNull IInlineSuggestionsRequestCallback callback,
- @NonNull String packageName) {
- mRequestInfo = requestInfo;
- mCallback = callback;
- mPackageName = packageName;
- }
- }
-
- /**
- * If a request to create inline autofill suggestions comes in while the IME is unbound
- * due to {@link CarInputMethodManagerService#mPreventImeStartupUnlessTextEditor},
- * this is where it is stored, so that it may be fulfilled once the IME rebinds.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- private CreateInlineSuggestionsRequest mPendingInlineSuggestionsRequest;
-
- /**
- * A callback into the autofill service obtained from the latest call to
- * {@link #onCreateInlineSuggestionsRequest}, which can be used to invalidate an
- * autofill session in case the IME process dies.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- private IInlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
-
- CarAutofillSuggestionsController(@NonNull CarInputMethodManagerService service) {
- mService = service;
- mMethodMap = mService.mMethodMap;
- mSettings = mService.mSettings;
- }
-
- @GuardedBy("ImfLock.class")
- @Override
- public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
- InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback,
- boolean touchExplorationEnabled) {
- clearPendingInlineSuggestionsRequest();
- mInlineSuggestionsRequestCallback = callback;
- final InputMethodInfo imi = mMethodMap.get(mService.getSelectedMethodIdLocked());
- try {
- if (userId == mSettings.getCurrentUserId()
- && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
- mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
- requestInfo, callback, imi.getPackageName());
- if (mService.getCurMethodLocked() != null) {
- // In the normal case when the IME is connected, we can make the request here.
- performOnCreateInlineSuggestionsRequest();
- } else {
- // Otherwise, the next time the IME connection is established,
- // InputMethodBindingController.mMainConnection#onServiceConnected() will call
- // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
- if (DEBUG) {
- Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
- }
- }
- } else {
- callback.onInlineSuggestionsUnsupported();
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e);
- }
- }
-
- @GuardedBy("ImfLock.class")
- public void performOnCreateInlineSuggestionsRequest() {
- if (mPendingInlineSuggestionsRequest == null) {
- return;
- }
- IInputMethodInvoker curMethod = mService.getCurMethodLocked();
- if (DEBUG) {
- Slog.d(TAG, "Performing onCreateInlineSuggestionsRequest. mCurMethod = " + curMethod);
- }
- if (curMethod != null) {
- final IInlineSuggestionsRequestCallback callback =
- new InlineSuggestionsRequestCallbackDecorator(
- mPendingInlineSuggestionsRequest.mCallback,
- mPendingInlineSuggestionsRequest.mPackageName,
- mService.getCurTokenDisplayIdLocked(),
- mService.getCurTokenLocked(),
- mService);
- curMethod.onCreateInlineSuggestionsRequest(
- mPendingInlineSuggestionsRequest.mRequestInfo, callback);
- } else {
- Slog.w(TAG, "No IME connected! Abandoning inline suggestions creation request.");
- }
- clearPendingInlineSuggestionsRequest();
- }
-
- @GuardedBy("ImfLock.class")
- private void clearPendingInlineSuggestionsRequest() {
- mPendingInlineSuggestionsRequest = null;
- }
-
- private static boolean isInlineSuggestionsEnabled(InputMethodInfo imi,
- boolean touchExplorationEnabled) {
- return imi.isInlineSuggestionsEnabled()
- && (!touchExplorationEnabled
- || imi.supportsInlineSuggestionsWithTouchExploration());
- }
-
- @GuardedBy("ImfLock.class")
- public void invalidateAutofillSession() {
- if (mInlineSuggestionsRequestCallback != null) {
- try {
- mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated();
- } catch (RemoteException e) {
- Slog.e(TAG, "Cannot invalidate autofill session.", e);
- }
- }
- }
-
- /**
- * The decorator which validates the host package name in the
- * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name.
- */
- private static final class InlineSuggestionsRequestCallbackDecorator
- extends IInlineSuggestionsRequestCallback.Stub {
- @NonNull private final IInlineSuggestionsRequestCallback mCallback;
- @NonNull private final String mImePackageName;
- private final int mImeDisplayId;
- @NonNull private final IBinder mImeToken;
- @NonNull private final CarInputMethodManagerService mImms;
-
- InlineSuggestionsRequestCallbackDecorator(
- @NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName,
- int displayId, @NonNull IBinder imeToken,
- @NonNull CarInputMethodManagerService imms) {
- mCallback = callback;
- mImePackageName = imePackageName;
- mImeDisplayId = displayId;
- mImeToken = imeToken;
- mImms = imms;
- }
-
- @Override
- public void onInlineSuggestionsUnsupported() throws RemoteException {
- mCallback.onInlineSuggestionsUnsupported();
- }
-
- @Override
- public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
- IInlineSuggestionsResponseCallback callback)
- throws RemoteException {
- if (!mImePackageName.equals(request.getHostPackageName())) {
- throw new SecurityException(
- "Host package name in the provide request=[" + request.getHostPackageName()
- + "] doesn't match the IME package name=[" + mImePackageName
- + "].");
- }
- request.setHostDisplayId(mImeDisplayId);
- mImms.setCurHostInputToken(mImeToken, request.getHostInputToken());
- mCallback.onInlineSuggestionsRequest(request, callback);
- }
-
- @Override
- public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
- mCallback.onInputMethodStartInput(imeFieldId);
- }
-
- @Override
- public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
- mCallback.onInputMethodShowInputRequested(requestResult);
- }
-
- @Override
- public void onInputMethodStartInputView() throws RemoteException {
- mCallback.onInputMethodStartInputView();
- }
-
- @Override
- public void onInputMethodFinishInputView() throws RemoteException {
- mCallback.onInputMethodFinishInputView();
- }
-
- @Override
- public void onInputMethodFinishInput() throws RemoteException {
- mCallback.onInputMethodFinishInput();
- }
-
- @Override
- public void onInlineSuggestionsSessionInvalidated() throws RemoteException {
- mCallback.onInlineSuggestionsSessionInvalidated();
- }
- }
-}
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/CarDefaultImeVisibilityApplier.java b/builtInServices/src_imms/com/android/server/inputmethod/CarDefaultImeVisibilityApplier.java
deleted file mode 100644
index 0b7f4c4..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/CarDefaultImeVisibilityApplier.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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.inputmethod;
-
-import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
-
-import static com.android.server.EventLogTags.IMF_HIDE_IME;
-import static com.android.server.EventLogTags.IMF_SHOW_IME;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_REMOVE_IME_SNAPSHOT;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_SNAPSHOT;
-
-import android.annotation.Nullable;
-import android.os.IBinder;
-import android.os.ResultReceiver;
-import android.util.EventLog;
-import android.util.Slog;
-import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.InputMethodManager;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.InputMethodDebug;
-import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.server.LocalServices;
-import com.android.server.wm.WindowManagerInternal;
-
-import java.util.Objects;
-
-/**
- * The default implementation of {@link ImeVisibilityApplier} used in
- * {@link CarInputMethodManagerService}.
- */
-final class CarDefaultImeVisibilityApplier implements ImeVisibilityApplier {
-
- private static final String TAG = "DefaultImeVisibilityApplier";
-
- private static final boolean DEBUG = CarInputMethodManagerService.DEBUG;
-
- private CarInputMethodManagerService mService;
-
- private final WindowManagerInternal mWindowManagerInternal;
-
-
- CarDefaultImeVisibilityApplier(CarInputMethodManagerService service) {
- mService = service;
- mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
- }
-
- @GuardedBy("ImfLock.class")
- @Override
- public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
- int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
- if (curMethod != null) {
- if (DEBUG) {
- Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
- + ", " + showFlags + ", " + resultReceiver + ") for reason: "
- + InputMethodDebug.softInputDisplayReasonToString(reason));
- }
- // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
- if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
- if (DEBUG_IME_VISIBILITY) {
- EventLog.writeEvent(IMF_SHOW_IME, statsToken.getTag(),
- Objects.toString(mService.mCurFocusedWindow),
- InputMethodDebug.softInputDisplayReasonToString(reason),
- InputMethodDebug.softInputModeToString(
- mService.mCurFocusedWindowSoftInputMode));
- }
- mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
- statsToken);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- @Override
- public void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
- if (curMethod != null) {
- // The IME will report its visible state again after the following message finally
- // 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.
- if (DEBUG) {
- Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
- + ", " + resultReceiver + ") for reason: "
- + InputMethodDebug.softInputDisplayReasonToString(reason));
- }
- // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
- if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) {
- if (DEBUG_IME_VISIBILITY) {
- EventLog.writeEvent(IMF_HIDE_IME, statsToken.getTag(),
- Objects.toString(mService.mCurFocusedWindow),
- InputMethodDebug.softInputDisplayReasonToString(reason),
- InputMethodDebug.softInputModeToString(
- mService.mCurFocusedWindowSoftInputMode));
- }
- mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
- statsToken);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- @Override
- public void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
- @ImeVisibilityStateComputer.VisibilityState int state) {
- applyImeVisibility(windowToken, statsToken, state, -1 /* ignore reason */);
- }
-
- @GuardedBy("ImfLock.class")
- void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
- @ImeVisibilityStateComputer.VisibilityState int state, int reason) {
- switch (state) {
- case STATE_SHOW_IME:
- 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.hasAttachedClient()) {
- 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
- // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
- // Send it to window manager to hide IME from IME target window.
- // Send it to window manager to hide IME from the actual IME control target
- // of the target display.
- mWindowManagerInternal.hideIme(windowToken,
- mService.getDisplayIdToShowImeLocked(), statsToken);
- } else {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
- }
- break;
- case STATE_HIDE_IME_EXPLICIT:
- mService.hideCurrentInputLocked(windowToken, statsToken, 0, null, reason);
- break;
- case STATE_HIDE_IME_NOT_ALWAYS:
- mService.hideCurrentInputLocked(windowToken, statsToken,
- InputMethodManager.HIDE_NOT_ALWAYS, null, reason);
- break;
- case STATE_SHOW_IME_IMPLICIT:
- mService.showCurrentInputLocked(windowToken, statsToken,
- InputMethodManager.SHOW_IMPLICIT, null, reason);
- break;
- case STATE_SHOW_IME_SNAPSHOT:
- showImeScreenshot(windowToken, mService.getDisplayIdToShowImeLocked(), null);
- break;
- case STATE_REMOVE_IME_SNAPSHOT:
- removeImeScreenshot(mService.getDisplayIdToShowImeLocked());
- break;
- default:
- throw new IllegalArgumentException("Invalid IME visibility state: " + state);
- }
- }
-}
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/CarImeVisibilityStateComputer.java b/builtInServices/src_imms/com/android/server/inputmethod/CarImeVisibilityStateComputer.java
deleted file mode 100644
index 101709e..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/CarImeVisibilityStateComputer.java
+++ /dev/null
@@ -1,738 +0,0 @@
-/*
- * 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.inputmethod;
-
-
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
-import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD;
-import static android.server.inputmethod.InputMethodManagerServiceProto.INPUT_SHOWN;
-import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_EXPLICITLY_REQUESTED;
-import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
-import static android.view.WindowManager.LayoutParams.SoftInputModeFlags;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-
-import static com.android.internal.inputmethod.InputMethodDebug.softInputModeToString;
-import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS;
-import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS;
-import static com.android.server.inputmethod.CarInputMethodManagerService.computeImeDisplayIdForTarget;
-
-import android.accessibilityservice.AccessibilityService;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.content.res.Configuration;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.PrintWriterPrinter;
-import android.util.Printer;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.WindowManager;
-import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.InputMethod;
-import android.view.inputmethod.InputMethodManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.server.LocalServices;
-import com.android.server.wm.ImeTargetChangeListener;
-import com.android.server.wm.WindowManagerInternal;
-
-import java.io.PrintWriter;
-import java.util.WeakHashMap;
-
-/**
- * A computer used by {@link CarInputMethodManagerService} that computes the IME visibility state
- * according the given {@link ImeTargetWindowState} from the focused window or the app requested IME
- * visibility from {@link InputMethodManager}.
- */
-public final class CarImeVisibilityStateComputer {
-
- private static final String TAG = "ImeVisibilityStateComputer";
-
- private static final boolean DEBUG = CarInputMethodManagerService.DEBUG;
-
- private final CarInputMethodManagerService mService;
- private final WindowManagerInternal mWindowManagerInternal;
-
- final CarInputMethodManagerService.ImeDisplayValidator mImeDisplayValidator;
-
- /**
- * A map used to track the requested IME target window and its state. The key represents the
- * token of the window and the value is the corresponding IME window state.
- */
- private final WeakHashMap<IBinder, ImeTargetWindowState> mRequestWindowStateMap =
- new WeakHashMap<>();
-
- /**
- * Set if IME was explicitly told to show the input method.
- *
- * @see InputMethodManager#SHOW_IMPLICIT that we set the value is {@code false}.
- * @see InputMethodManager#HIDE_IMPLICIT_ONLY that system will not hide IME when the value is
- * {@code true}.
- */
- boolean mRequestedShowExplicitly;
-
- /**
- * Set if we were forced to be shown.
- *
- * @see InputMethodManager#SHOW_FORCED
- * @see InputMethodManager#HIDE_NOT_ALWAYS
- */
- boolean mShowForced;
-
- /**
- * Set if we last told the input method to show itself.
- */
- private boolean mInputShown;
-
- /**
- * Set if we called
- * {@link com.android.server.wm.ImeTargetVisibilityPolicy#showImeScreenshot(IBinder, int)}.
- */
- private boolean mRequestedImeScreenshot;
-
- /** The window token of the current visible IME layering target overlay. */
- private IBinder mCurVisibleImeLayeringOverlay;
-
- /** The window token of the current visible IME input target. */
- private IBinder mCurVisibleImeInputTarget;
-
- /** Represent the invalid IME visibility state */
- public static final int STATE_INVALID = -1;
-
- /** State to handle hiding the IME window requested by the app. */
- public static final int STATE_HIDE_IME = 0;
-
- /** State to handle showing the IME window requested by the app. */
- public static final int STATE_SHOW_IME = 1;
-
- /** State to handle showing the IME window with making the overlay window above it. */
- public static final int STATE_SHOW_IME_ABOVE_OVERLAY = 2;
-
- /** State to handle showing the IME window with making the overlay window behind it. */
- public static final int STATE_SHOW_IME_BEHIND_OVERLAY = 3;
-
- /** State to handle showing an IME preview surface during the app was loosing the IME focus */
- public static final int STATE_SHOW_IME_SNAPSHOT = 4;
-
- public static final int STATE_HIDE_IME_EXPLICIT = 5;
-
- public static final int STATE_HIDE_IME_NOT_ALWAYS = 6;
-
- public static final int STATE_SHOW_IME_IMPLICIT = 7;
-
- /** State to handle removing an IME preview surface when necessary. */
- public static final int STATE_REMOVE_IME_SNAPSHOT = 8;
-
- @IntDef({
- STATE_INVALID,
- STATE_HIDE_IME,
- STATE_SHOW_IME,
- STATE_SHOW_IME_ABOVE_OVERLAY,
- STATE_SHOW_IME_BEHIND_OVERLAY,
- STATE_SHOW_IME_SNAPSHOT,
- STATE_HIDE_IME_EXPLICIT,
- STATE_HIDE_IME_NOT_ALWAYS,
- STATE_SHOW_IME_IMPLICIT,
- STATE_REMOVE_IME_SNAPSHOT,
- })
- @interface VisibilityState {}
-
- /**
- * The policy to configure the IME visibility.
- */
- private final ImeVisibilityPolicy mPolicy;
-
- public CarImeVisibilityStateComputer(@NonNull CarInputMethodManagerService service) {
- this(service,
- LocalServices.getService(WindowManagerInternal.class),
- LocalServices.getService(WindowManagerInternal.class)::getDisplayImePolicy,
- new ImeVisibilityPolicy());
- }
-
- @VisibleForTesting
- public CarImeVisibilityStateComputer(@NonNull CarInputMethodManagerService service,
- @NonNull Injector injector) {
- this(service, injector.getWmService(), injector.getImeValidator(),
- new ImeVisibilityPolicy());
- }
-
- interface Injector {
- default WindowManagerInternal getWmService() {
- return null;
- }
-
- default CarInputMethodManagerService.ImeDisplayValidator getImeValidator() {
- return null;
- }
- }
-
- private CarImeVisibilityStateComputer(CarInputMethodManagerService service,
- WindowManagerInternal wmService,
- CarInputMethodManagerService.ImeDisplayValidator imeDisplayValidator,
- ImeVisibilityPolicy imePolicy) {
- mService = service;
- mWindowManagerInternal = wmService;
- mImeDisplayValidator = imeDisplayValidator;
- mPolicy = imePolicy;
- mWindowManagerInternal.setInputMethodTargetChangeListener(new ImeTargetChangeListener() {
- @Override
- public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
- @WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed) {
- mCurVisibleImeLayeringOverlay =
- // Ignoring the starting window since it's ok to cover the IME target
- // window in temporary without affecting the IME visibility.
- (visible && !removed && windowType != TYPE_APPLICATION_STARTING)
- ? overlayWindowToken : null;
- }
-
- @Override
- public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
- boolean visibleRequested, boolean removed) {
- if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed)
- && mCurVisibleImeLayeringOverlay != null) {
- mService.onApplyImeVisibilityFromComputer(imeInputTarget,
- new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
- SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE));
- }
- mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null;
- }
- });
- }
-
- /**
- * Called when {@link CarInputMethodManagerService} is processing the show IME request.
- * @param statsToken The token for tracking this show request
- * @param showFlags The additional operation flags to indicate whether this show request mode is
- * implicit or explicit.
- * @return {@code true} when the computer has proceed this show request operation.
- */
- boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) {
- if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
- return false;
- }
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
- if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
- mRequestedShowExplicitly = true;
- mShowForced = true;
- } else if ((showFlags & InputMethodManager.SHOW_IMPLICIT) == 0) {
- mRequestedShowExplicitly = true;
- }
- return true;
- }
-
- /**
- * Called when {@link CarInputMethodManagerService} is processing the hide IME request.
- * @param statsToken The token for tracking this hide request
- * @param hideFlags The additional operation flags to indicate whether this hide request mode is
- * implicit or explicit.
- * @return {@code true} when the computer has proceed this hide request operations.
- */
- boolean canHideIme(@NonNull ImeTracker.Token statsToken, int hideFlags) {
- 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.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.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
- return false;
- }
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
- return true;
- }
-
- int getImeShowFlags() {
- int flags = 0;
- if (mShowForced) {
- flags |= InputMethod.SHOW_FORCED | InputMethod.SHOW_EXPLICIT;
- } else if (mRequestedShowExplicitly) {
- flags |= InputMethod.SHOW_EXPLICIT;
- } else {
- flags |= InputMethodManager.SHOW_IMPLICIT;
- }
- return flags;
- }
-
- void clearImeShowFlags() {
- mRequestedShowExplicitly = false;
- mShowForced = false;
- mInputShown = false;
- }
-
- int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) {
- final int displayToShowIme = computeImeDisplayIdForTarget(displayId, mImeDisplayValidator);
- state.setImeDisplayId(displayToShowIme);
- final boolean imeHiddenByPolicy = displayToShowIme == INVALID_DISPLAY;
- mPolicy.setImeHiddenByDisplayPolicy(imeHiddenByPolicy);
- return displayToShowIme;
- }
-
- /**
- * Request to show/hide IME from the given window.
- *
- * @param windowToken The window which requests to show/hide IME.
- * @param showIme {@code true} means to show IME, {@code false} otherwise.
- * Note that in the computer will take this option to compute the
- * visibility state, it could be {@link #STATE_SHOW_IME} or
- * {@link #STATE_HIDE_IME}.
- */
- void requestImeVisibility(IBinder windowToken, boolean showIme) {
- ImeTargetWindowState state = getOrCreateWindowState(windowToken);
- if (!mPolicy.mPendingA11yRequestingHideKeyboard) {
- state.setRequestedImeVisible(showIme);
- } else {
- // As A11y requests no IME is just a temporary, so we don't change the requested IME
- // visible in case the last visibility state goes wrong after leaving from the a11y
- // policy.
- mPolicy.mPendingA11yRequestingHideKeyboard = false;
- }
- // create a placeholder token for IMS so that IMS cannot inject windows into client app.
- state.setRequestImeToken(new Binder());
- setWindowStateInner(windowToken, state);
- }
-
- ImeTargetWindowState getOrCreateWindowState(IBinder windowToken) {
- ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
- if (state == null) {
- state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNSPECIFIED, 0, false, false, false);
- }
- return state;
- }
-
- ImeTargetWindowState getWindowStateOrNull(IBinder windowToken) {
- ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
- return state;
- }
-
- void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
- final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
- if (state != null && newState.hasEditorFocused()) {
- // Inherit the last requested IME visible state when the target window is still
- // focused with an editor.
- newState.setRequestedImeVisible(state.mRequestedImeVisible);
- }
- setWindowStateInner(windowToken, newState);
- }
-
- private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
- if (DEBUG) {
- Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken
- + ", state=" + newState);
- }
- mRequestWindowStateMap.put(windowToken, newState);
- }
-
- static class ImeVisibilityResult {
- private final @VisibilityState int mState;
- private final @SoftInputShowHideReason int mReason;
-
- ImeVisibilityResult(@VisibilityState int state, @SoftInputShowHideReason int reason) {
- mState = state;
- mReason = reason;
- }
-
- @VisibilityState int getState() {
- return mState;
- }
-
- @SoftInputShowHideReason int getReason() {
- return mReason;
- }
- }
-
- ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) {
- // TODO: Output the request IME visibility state according to the requested window state
- final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE;
- // Should we auto-show the IME even if the caller has not
- // specified what should be done with it?
- // We only do this automatically if the window can resize
- // to accommodate the IME (so what the user sees will give
- // them good context without input information being obscured
- // by the IME) or if running on a large screen where there
- // is more room for the target window + IME.
- final boolean doAutoShow =
- (state.mSoftInputModeState & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
- == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
- || mService.mRes.getConfiguration().isLayoutSizeAtLeast(
- Configuration.SCREENLAYOUT_SIZE_LARGE);
- final boolean isForwardNavigation = (state.mSoftInputModeState
- & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0;
-
- // We shows the IME when the system allows the IME focused target window to restore the
- // IME visibility (e.g. switching to the app task when last time the IME is visible).
- // Note that we don't restore IME visibility for some cases (e.g. when the soft input
- // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
- // Because the app might leverage these flags to hide soft-keyboard with showing their own
- // UI for input.
- if (state.hasEditorFocused() && shouldRestoreImeVisibility(state)) {
- if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
- // Inherit the last requested IME visible state when the target window is still
- // focused with an editor.
- state.setRequestedImeVisible(true);
- setWindowStateInner(getWindowTokenFrom(state), state);
- return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
- SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
- }
-
- switch (softInputVisibility) {
- case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
- if (state.hasImeFocusChanged() && (!state.hasEditorFocused() || !doAutoShow)) {
- if (WindowManager.LayoutParams.mayUseInputMethod(state.getWindowFlags())) {
- // There is no focus view, and this window will
- // be behind any soft input window, so hide the
- // soft input window if it is shown.
- if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
- return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS,
- SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
- }
- } else if (state.hasEditorFocused() && doAutoShow && isForwardNavigation) {
- // There is a focus view, and we are navigating forward
- // into the window, so show the input window for the user.
- // We only do this automatically if the window can resize
- // to accommodate the IME (so what the user sees will give
- // them good context without input information being obscured
- // by the IME) or if running on a large screen where there
- // is more room for the target window + IME.
- if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
- return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
- SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
- }
- break;
- case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
- // 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) {
- if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
- return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
- SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
- }
- break;
- case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
- if (state.hasImeFocusChanged()) {
- if (DEBUG) Slog.v(TAG, "Window asks to hide input");
- return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
- SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
- }
- break;
- case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
- if (isForwardNavigation) {
- if (allowVisible) {
- if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
- return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
- SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
- } else {
- Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
- + " there is no focused view that also returns true from"
- + " View#onCheckIsTextEditor()");
- }
- }
- break;
- case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
- if (DEBUG) Slog.v(TAG, "Window asks to always show input");
- if (allowVisible) {
- if (state.hasImeFocusChanged()) {
- return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
- SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
- }
- } else {
- Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
- + " there is no focused view that also returns true from"
- + " View#onCheckIsTextEditor()");
- }
- break;
- }
-
- if (!state.hasImeFocusChanged()) {
- // On previous platforms, when Dialogs re-gained focus, the Activity behind
- // would briefly gain focus first, and dismiss the IME.
- // On R that behavior has been fixed, but unfortunately apps have come
- // to rely on this behavior to hide the IME when the editor no longer has focus
- // To maintain compatibility, we are now hiding the IME when we don't have
- // an editor upon refocusing a window.
- if (state.isStartInputByGainFocus()) {
- if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
- return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
- SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
- }
- }
- if (!state.hasEditorFocused() && mInputShown && state.isStartInputByGainFocus()
- && mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) {
- // Hide the soft-keyboard when the system do nothing for softInputModeState
- // of the window being gained focus without an editor. This behavior benefits
- // to resolve some unexpected IME visible cases while that window with following
- // configurations being switched from an IME shown window:
- // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor
- // 2) SOFT_INPUT_STATE_VISIBLE state without an editor
- // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
- if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
- return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
- SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
- }
- return null;
- }
-
- @VisibleForTesting
- ImeVisibilityResult onInteractiveChanged(IBinder windowToken, boolean interactive) {
- final ImeTargetWindowState state = getWindowStateOrNull(windowToken);
- if (state != null && state.isRequestedImeVisible() && mInputShown && !interactive) {
- mRequestedImeScreenshot = true;
- return new ImeVisibilityResult(STATE_SHOW_IME_SNAPSHOT, SHOW_IME_SCREENSHOT_FROM_IMMS);
- }
- if (interactive && mRequestedImeScreenshot) {
- mRequestedImeScreenshot = false;
- return new ImeVisibilityResult(STATE_REMOVE_IME_SNAPSHOT,
- REMOVE_IME_SCREENSHOT_FROM_IMMS);
- }
- return null;
- }
-
- IBinder getWindowTokenFrom(IBinder requestImeToken) {
- for (IBinder windowToken : mRequestWindowStateMap.keySet()) {
- final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
- if (state.getRequestImeToken() == requestImeToken) {
- return windowToken;
- }
- }
- // Fallback to the focused window for some edge cases (e.g. relaunching the activity)
- return mService.mCurFocusedWindow;
- }
-
- IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
- for (IBinder windowToken : mRequestWindowStateMap.keySet()) {
- final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
- if (state == windowState) {
- return windowToken;
- }
- }
- return null;
- }
-
- boolean shouldRestoreImeVisibility(@NonNull ImeTargetWindowState state) {
- final int softInputMode = state.getSoftInputModeState();
- switch (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
- case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
- return false;
- case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
- if ((softInputMode & SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
- return false;
- }
- }
- return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state));
- }
-
- boolean isInputShown() {
- return mInputShown;
- }
-
- void setInputShown(boolean inputShown) {
- mInputShown = inputShown;
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly);
- proto.write(SHOW_FORCED, mShowForced);
- proto.write(ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD,
- mPolicy.isA11yRequestNoSoftKeyboard());
- proto.write(INPUT_SHOWN, mInputShown);
- }
-
- void dump(PrintWriter pw) {
- final Printer p = new PrintWriterPrinter(pw);
- p.println(" mRequestedShowExplicitly=" + mRequestedShowExplicitly
- + " mShowForced=" + mShowForced);
- p.println(" mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy());
- p.println(" mInputShown=" + mInputShown);
- }
-
- /**
- * A settings class to manage all IME related visibility policies or settings.
- *
- * This is used for the visibility computer to manage and tell
- * {@link CarInputMethodManagerService} if the requested IME visibility is valid from
- * application call or the focus window.
- */
- static class ImeVisibilityPolicy {
- /**
- * {@code true} if the Ime policy has been set to
- * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
- *
- * This prevents the IME from showing when it otherwise may have shown.
- */
- private boolean mImeHiddenByDisplayPolicy;
-
- /**
- * Set when the accessibility service requests to hide IME by
- * {@link AccessibilityService.SoftKeyboardController#setShowMode}
- */
- private boolean mA11yRequestingNoSoftKeyboard;
-
- /**
- * Used when A11y request to hide IME temporary when receiving
- * {@link AccessibilityService#SHOW_MODE_HIDDEN} from
- * {@link android.provider.Settings.Secure#ACCESSIBILITY_SOFT_KEYBOARD_MODE} without
- * changing the requested IME visible state.
- */
- private boolean mPendingA11yRequestingHideKeyboard;
-
- void setImeHiddenByDisplayPolicy(boolean hideIme) {
- mImeHiddenByDisplayPolicy = hideIme;
- }
-
- boolean isImeHiddenByDisplayPolicy() {
- return mImeHiddenByDisplayPolicy;
- }
-
- void setA11yRequestNoSoftKeyboard(int keyboardShowMode) {
- mA11yRequestingNoSoftKeyboard =
- (keyboardShowMode & AccessibilityService.SHOW_MODE_MASK) == SHOW_MODE_HIDDEN;
- if (mA11yRequestingNoSoftKeyboard) {
- mPendingA11yRequestingHideKeyboard = true;
- }
- }
-
- boolean isA11yRequestNoSoftKeyboard() {
- return mA11yRequestingNoSoftKeyboard;
- }
- }
-
- ImeVisibilityPolicy getImePolicy() {
- return mPolicy;
- }
-
- /**
- * A class that represents the current state of the IME target window.
- */
- static class ImeTargetWindowState {
- ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
- boolean imeFocusChanged, boolean hasFocusedEditor,
- boolean isStartInputByGainFocus) {
- mSoftInputModeState = softInputModeState;
- mWindowFlags = windowFlags;
- mImeFocusChanged = imeFocusChanged;
- mHasFocusedEditor = hasFocusedEditor;
- mIsStartInputByGainFocus = isStartInputByGainFocus;
- }
-
- /**
- * Visibility state for this window. By default no state has been specified.
- */
- private final @SoftInputModeFlags int mSoftInputModeState;
-
- private final int mWindowFlags;
-
- /**
- * {@code true} means the IME focus changed from the previous window, {@code false}
- * otherwise.
- */
- private final boolean mImeFocusChanged;
-
- /**
- * {@code true} when the window has focused an editor, {@code false} otherwise.
- */
- private final boolean mHasFocusedEditor;
-
- private final boolean mIsStartInputByGainFocus;
-
- /**
- * Set if the client has asked for the input method to be shown.
- */
- private boolean mRequestedImeVisible;
-
- /**
- * A identifier for knowing the requester of {@link InputMethodManager#showSoftInput} or
- * {@link InputMethodManager#hideSoftInputFromWindow}.
- */
- private IBinder mRequestImeToken;
-
- /**
- * The IME target display id for which the latest startInput was called.
- */
- private int mImeDisplayId = DEFAULT_DISPLAY;
-
- boolean hasImeFocusChanged() {
- return mImeFocusChanged;
- }
-
- boolean hasEditorFocused() {
- return mHasFocusedEditor;
- }
-
- boolean isStartInputByGainFocus() {
- return mIsStartInputByGainFocus;
- }
-
- int getSoftInputModeState() {
- return mSoftInputModeState;
- }
-
- int getWindowFlags() {
- return mWindowFlags;
- }
-
- private void setImeDisplayId(int imeDisplayId) {
- mImeDisplayId = imeDisplayId;
- }
-
- int getImeDisplayId() {
- return mImeDisplayId;
- }
-
- private void setRequestedImeVisible(boolean requestedImeVisible) {
- mRequestedImeVisible = requestedImeVisible;
- }
-
- boolean isRequestedImeVisible() {
- return mRequestedImeVisible;
- }
-
- void setRequestImeToken(IBinder token) {
- mRequestImeToken = token;
- }
-
- IBinder getRequestImeToken() {
- return mRequestImeToken;
- }
-
- @Override
- public String toString() {
- return "ImeTargetWindowState{ imeToken " + mRequestImeToken
- + " imeFocusChanged " + mImeFocusChanged
- + " hasEditorFocused " + mHasFocusedEditor
- + " requestedImeVisible " + mRequestedImeVisible
- + " imeDisplayId " + mImeDisplayId
- + " softInputModeState " + softInputModeToString(mSoftInputModeState)
- + " isStartInputByGainFocus " + mIsStartInputByGainFocus
- + "}";
- }
- }
-}
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodBindingController.java b/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodBindingController.java
deleted file mode 100644
index db5e4f6..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodBindingController.java
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManagerInternal;
-import android.inputmethodservice.InputMethodService;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.EventLog;
-import android.util.Slog;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethod;
-import android.view.inputmethod.InputMethodInfo;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.inputmethod.IInputMethod;
-import com.android.internal.inputmethod.InputBindResult;
-import com.android.internal.inputmethod.UnbindReason;
-import com.android.server.EventLogTags;
-import com.android.server.wm.WindowManagerInternal;
-
-import java.util.concurrent.CountDownLatch;
-
-/**
- * A controller managing the state of the input method binding.
- */
-final class CarInputMethodBindingController {
- static final boolean DEBUG = false;
- private static final String TAG = CarInputMethodBindingController.class.getSimpleName();
-
- /** Time in milliseconds that the IME service has to bind before it is reconnected. */
- static final long TIME_TO_RECONNECT = 3 * 1000;
-
- @NonNull private final CarInputMethodManagerService mService;
- @NonNull private final Context mContext;
- @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap;
- @NonNull private final InputMethodUtils.InputMethodSettings mSettings;
- @NonNull private final PackageManagerInternal mPackageManagerInternal;
- @NonNull private final WindowManagerInternal mWindowManagerInternal;
-
- @GuardedBy("ImfLock.class") private long mLastBindTime;
- @GuardedBy("ImfLock.class") private boolean mHasConnection;
- @GuardedBy("ImfLock.class") @Nullable private String mCurId;
- @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId;
- @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent;
- @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
- @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
- @GuardedBy("ImfLock.class") @Nullable private IBinder mCurToken;
- @GuardedBy("ImfLock.class") private int mCurSeq;
- @GuardedBy("ImfLock.class") private boolean mVisibleBound;
- @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
-
- @Nullable private CountDownLatch mLatchForTesting;
-
- /**
- * Binding flags for establishing connection to the {@link InputMethodService}.
- */
- @VisibleForTesting
- static final int IME_CONNECTION_BIND_FLAGS =
- Context.BIND_AUTO_CREATE
- | Context.BIND_NOT_VISIBLE
- | Context.BIND_NOT_FOREGROUND
- | Context.BIND_IMPORTANT_BACKGROUND
- | Context.BIND_SCHEDULE_LIKE_TOP_APP;
-
- private final int mImeConnectionBindFlags;
-
- /**
- * Binding flags used only while the {@link InputMethodService} is showing window.
- */
- @VisibleForTesting
- static final int IME_VISIBLE_BIND_FLAGS =
- Context.BIND_AUTO_CREATE
- | Context.BIND_TREAT_LIKE_ACTIVITY
- | Context.BIND_FOREGROUND_SERVICE
- | Context.BIND_INCLUDE_CAPABILITIES
- | Context.BIND_SHOWING_UI;
-
- CarInputMethodBindingController(@NonNull CarInputMethodManagerService service) {
- this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
- }
-
- CarInputMethodBindingController(@NonNull CarInputMethodManagerService service,
- int imeConnectionBindFlags, CountDownLatch latchForTesting) {
- mService = service;
- mContext = mService.mContext;
- mMethodMap = mService.mMethodMap;
- mSettings = mService.mSettings;
- mPackageManagerInternal = mService.mPackageManagerInternal;
- mWindowManagerInternal = mService.mWindowManagerInternal;
- mImeConnectionBindFlags = imeConnectionBindFlags;
- mLatchForTesting = latchForTesting;
- }
-
- /**
- * Time that we last initiated a bind to the input method, to determine
- * if we should try to disconnect and reconnect to it.
- */
- @GuardedBy("ImfLock.class")
- long getLastBindTime() {
- return mLastBindTime;
- }
-
- /**
- * Set to true if our ServiceConnection is currently actively bound to
- * a service (whether or not we have gotten its IBinder back yet).
- */
- @GuardedBy("ImfLock.class")
- boolean hasConnection() {
- return mHasConnection;
- }
-
- /**
- * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
- * connected to or in the process of connecting to.
- *
- * <p>This can be {@code null} when no input method is connected.</p>
- *
- * @see #getSelectedMethodId()
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- String getCurId() {
- return mCurId;
- }
-
- /**
- * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
- * This is to be synchronized with the secure settings keyed with
- * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD}.
- *
- * <p>This can be transiently {@code null} when the system is re-initializing input method
- * settings, e.g., the system locale is just changed.</p>
- *
- * <p>Note that {@link #getCurId()} is used to track which IME is being connected to
- * {@link com.android.server.inputmethod.CarInputMethodManagerService}.</p>
- *
- * @see #getCurId()
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- String getSelectedMethodId() {
- return mSelectedMethodId;
- }
-
- @GuardedBy("ImfLock.class")
- void setSelectedMethodId(@Nullable String selectedMethodId) {
- mSelectedMethodId = selectedMethodId;
- }
-
- /**
- * The token we have made for the currently active input method, to
- * identify it in the future.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- IBinder getCurToken() {
- return mCurToken;
- }
-
- /**
- * The Intent used to connect to the current input method.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- Intent getCurIntent() {
- return mCurIntent;
- }
-
- /**
- * The current binding sequence number, incremented every time there is
- * a new bind performed.
- */
- @GuardedBy("ImfLock.class")
- int getSequenceNumber() {
- return mCurSeq;
- }
-
- /**
- * Increase the current binding sequence number by one.
- * Reset to 1 on overflow.
- */
- @GuardedBy("ImfLock.class")
- void advanceSequenceNumber() {
- mCurSeq += 1;
- if (mCurSeq <= 0) {
- mCurSeq = 1;
- }
- }
-
- /**
- * If non-null, this is the input method service we are currently connected
- * to.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- IInputMethodInvoker getCurMethod() {
- return mCurMethod;
- }
-
- /**
- * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}.
- */
- @GuardedBy("ImfLock.class")
- int getCurMethodUid() {
- return mCurMethodUid;
- }
-
- /**
- * Indicates whether {@link #mVisibleConnection} is currently in use.
- */
- @GuardedBy("ImfLock.class")
- boolean isVisibleBound() {
- return mVisibleBound;
- }
-
- /**
- * Returns {@code true} if current IME supports Stylus Handwriting.
- */
- boolean supportsStylusHandwriting() {
- return mSupportsStylusHw;
- }
-
- /**
- * Used to bring IME service up to visible adjustment while it is being shown.
- */
- @GuardedBy("ImfLock.class")
- private final ServiceConnection mVisibleConnection = new ServiceConnection() {
- @Override public void onBindingDied(ComponentName name) {
- synchronized (ImfLock.class) {
- mService.invalidateAutofillSessionLocked();
- if (isVisibleBound()) {
- unbindVisibleConnection();
- }
- }
- }
-
- @Override public void onServiceConnected(ComponentName name, IBinder service) {
- }
-
- @Override public void onServiceDisconnected(ComponentName name) {
- synchronized (ImfLock.class) {
- mService.invalidateAutofillSessionLocked();
- }
- }
- };
-
- /**
- * Used to bind the IME while it is not currently being shown.
- */
- @GuardedBy("ImfLock.class")
- private final ServiceConnection mMainConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
- synchronized (ImfLock.class) {
- if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
- mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service));
- updateCurrentMethodUid();
- if (mCurToken == null) {
- Slog.w(TAG, "Service connected without a token!");
- unbindCurrentMethod();
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- return;
- }
- if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
- final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
- mSupportsStylusHw = info.supportsStylusHandwriting();
- mService.initializeImeLocked(mCurMethod, mCurToken);
- mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
- mService.reRequestCurrentClientSessionLocked();
- mService.performOnCreateInlineSuggestionsRequestLocked();
- }
-
- // reset Handwriting event receiver.
- // always call this as it handles changes in mSupportsStylusHw. It is a noop
- // if unchanged.
- mService.scheduleResetStylusHandwriting();
- }
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
- if (mLatchForTesting != null) {
- mLatchForTesting.countDown(); // Notify the finish to tests
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void updateCurrentMethodUid() {
- final String curMethodPackage = mCurIntent.getComponent().getPackageName();
- final int curMethodUid = mPackageManagerInternal.getPackageUid(
- curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId());
- if (curMethodUid < 0) {
- Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
- mCurMethodUid = Process.INVALID_UID;
- } else {
- mCurMethodUid = curMethodUid;
- }
- }
-
- @Override
- public void onServiceDisconnected(@NonNull ComponentName name) {
- // Note that mContext.unbindService(this) does not trigger this. Hence if we are
- // here the
- // disconnection is not intended by IMMS (e.g. triggered because the current IMS
- // crashed),
- // which is irregular but can eventually happen for everyone just by continuing
- // using the
- // device. Thus it is important to make sure that all the internal states are
- // properly
- // refreshed when this method is called back. Running
- // adb install -r <APK that implements the current IME>
- // would be a good way to trigger such a situation.
- synchronized (ImfLock.class) {
- if (DEBUG) {
- Slog.v(TAG, "Service disconnected: " + name + " mCurIntent=" + mCurIntent);
- }
- if (mCurMethod != null && mCurIntent != null
- && name.equals(mCurIntent.getComponent())) {
- // We consider this to be a new bind attempt, since the system
- // should now try to restart the service for us.
- mLastBindTime = SystemClock.uptimeMillis();
- clearCurMethodAndSessions();
- mService.clearInputShownLocked();
- mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
- mService.resetSystemUiLocked();
- }
- }
- }
- };
-
- @GuardedBy("ImfLock.class")
- void unbindCurrentMethod() {
- if (isVisibleBound()) {
- unbindVisibleConnection();
- }
-
- if (hasConnection()) {
- unbindMainConnection();
- }
-
- if (getCurToken() != null) {
- removeCurrentToken();
- mService.resetSystemUiLocked();
- }
-
- mCurId = null;
- clearCurMethodAndSessions();
- }
-
- @GuardedBy("ImfLock.class")
- private void clearCurMethodAndSessions() {
- mService.clearClientSessionsLocked();
- mCurMethod = null;
- mCurMethodUid = Process.INVALID_UID;
- }
-
- @GuardedBy("ImfLock.class")
- private void removeCurrentToken() {
- int curTokenDisplayId = mService.getCurTokenDisplayIdLocked();
-
- if (DEBUG) {
- Slog.v(TAG,
- "Removing window token: " + mCurToken + " for display: " + curTokenDisplayId);
- }
- mWindowManagerInternal.removeWindowToken(mCurToken, false /* removeWindows */,
- false /* animateExit */, curTokenDisplayId);
- mCurToken = null;
- }
-
- @GuardedBy("ImfLock.class")
- @NonNull
- InputBindResult bindCurrentMethod() {
- if (mSelectedMethodId == null) {
- Slog.e(TAG, "mSelectedMethodId is null!");
- return InputBindResult.NO_IME;
- }
-
- InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
- if (info == null) {
- throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
- }
-
- mCurIntent = createImeBindingIntent(info.getComponent());
-
- if (bindCurrentInputMethodServiceMainConnection()) {
- mCurId = info.getId();
- mLastBindTime = SystemClock.uptimeMillis();
-
- addFreshWindowToken();
- return new InputBindResult(
- InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
- null, null, null, mCurId, mCurSeq, null, false);
- }
-
- Slog.w(CarInputMethodManagerService.TAG,
- "Failure connecting to input method service: " + mCurIntent);
- mCurIntent = null;
- return InputBindResult.IME_NOT_CONNECTED;
- }
-
- @NonNull
- private Intent createImeBindingIntent(ComponentName component) {
- Intent intent = new Intent(InputMethod.SERVICE_INTERFACE);
- intent.setComponent(component);
- intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
- com.android.internal.R.string.input_method_binding_label);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
- mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
- PendingIntent.FLAG_IMMUTABLE));
- return intent;
- }
-
- @GuardedBy("ImfLock.class")
- private void addFreshWindowToken() {
- int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
- mCurToken = new Binder();
-
- mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
-
- if (DEBUG) {
- Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
- + displayIdToShowIme);
- }
- mWindowManagerInternal.addWindowToken(mCurToken,
- WindowManager.LayoutParams.TYPE_INPUT_METHOD,
- displayIdToShowIme, null /* options */);
- }
-
- @GuardedBy("ImfLock.class")
- private void unbindMainConnection() {
- mContext.unbindService(mMainConnection);
- mHasConnection = false;
- }
-
- @GuardedBy("ImfLock.class")
- void unbindVisibleConnection() {
- mContext.unbindService(mVisibleConnection);
- mVisibleBound = false;
- }
-
- @GuardedBy("ImfLock.class")
- private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
- if (mCurIntent == null || conn == null) {
- Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
- return false;
- }
- return mContext.bindServiceAsUser(mCurIntent, conn, flags,
- new UserHandle(mSettings.getCurrentUserId()));
- }
-
- @GuardedBy("ImfLock.class")
- private boolean bindCurrentInputMethodServiceMainConnection() {
- mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags);
- return mHasConnection;
- }
-
- /**
- * Bind the IME so that it can be shown.
- *
- * <p>
- * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds.
- */
- @GuardedBy("ImfLock.class")
- void setCurrentMethodVisible() {
- if (mCurMethod != null) {
- if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
- if (hasConnection() && !isVisibleBound()) {
- mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
- IME_VISIBLE_BIND_FLAGS);
- }
- return;
- }
-
- // No IME is currently connected. Reestablish the main connection.
- if (!hasConnection()) {
- if (DEBUG) {
- Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
- }
- bindCurrentMethod();
- return;
- }
-
- long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime;
- if (bindingDuration >= TIME_TO_RECONNECT) {
- // The client has asked to have the input method shown, but
- // we have been sitting here too long with a connection to the
- // service and no interface received, so let's disconnect/connect
- // to try to prod things along.
- EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
- bindingDuration, 1);
- Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisible()");
- unbindMainConnection();
- bindCurrentInputMethodServiceMainConnection();
- } else {
- if (DEBUG) {
- Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = "
- + (TIME_TO_RECONNECT - bindingDuration));
- }
- }
- }
-
- /**
- * Remove the binding needed for the IME to be shown.
- */
- @GuardedBy("ImfLock.class")
- void setCurrentMethodNotVisible() {
- if (isVisibleBound()) {
- unbindVisibleConnection();
- }
- }
-}
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodManagerService.java b/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodManagerService.java
deleted file mode 100644
index 3311d22..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodManagerService.java
+++ /dev/null
@@ -1,6889 +0,0 @@
-/*
- * 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.inputmethod;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.provider.Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE;
-import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED;
-import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
-import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_SEQ;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN_DISPLAY_ID;
-import static android.server.inputmethod.InputMethodManagerServiceProto.HAVE_CONNECTION;
-import static android.server.inputmethod.InputMethodManagerServiceProto.IME_WINDOW_VISIBILITY;
-import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
-import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
-import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
-import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID;
-import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
-import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-
-import static com.android.server.inputmethod.CarImeVisibilityStateComputer.ImeTargetWindowState;
-import static com.android.server.inputmethod.CarImeVisibilityStateComputer.ImeVisibilityResult;
-import static com.android.server.inputmethod.CarInputMethodBindingController.TIME_TO_RECONNECT;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
-import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.Manifest;
-import android.annotation.AnyThread;
-import android.annotation.BinderThread;
-import android.annotation.DrawableRes;
-import android.annotation.DurationMillisLong;
-import android.annotation.EnforcePermission;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UiThread;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.graphics.Matrix;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManager;
-import android.inputmethodservice.InputMethodService;
-import android.media.AudioManagerInternal;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.LocaleList;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Parcel;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
-import android.os.ShellCommand;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.EventLog;
-import android.util.IndentingPrintWriter;
-import android.util.IntArray;
-import android.util.Pair;
-import android.util.PrintWriterPrinter;
-import android.util.Printer;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
-import android.view.InputChannel;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.WindowManager;
-import android.view.WindowManager.DisplayImePolicy;
-import android.view.WindowManager.LayoutParams;
-import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.InputBinding;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethod;
-import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
-import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto;
-import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodManagerServiceTraceFileProto;
-import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodManagerServiceTraceProto;
-import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceFileProto;
-import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceProto;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-import android.window.ImeOnBackInvokedDispatcher;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
-import com.android.internal.inputmethod.IImeTracker;
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
-import com.android.internal.inputmethod.IInputContentUriToken;
-import com.android.internal.inputmethod.IInputMethod;
-import com.android.internal.inputmethod.IInputMethodClient;
-import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
-import com.android.internal.inputmethod.IInputMethodSession;
-import com.android.internal.inputmethod.IInputMethodSessionCallback;
-import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
-import com.android.internal.inputmethod.IRemoteInputConnection;
-import com.android.internal.inputmethod.ImeTracing;
-import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
-import com.android.internal.inputmethod.InputBindResult;
-import com.android.internal.inputmethod.InputMethodDebug;
-import com.android.internal.inputmethod.InputMethodNavButtonFlags;
-import com.android.internal.inputmethod.InputMethodSubtypeHandle;
-import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.internal.inputmethod.StartInputFlags;
-import com.android.internal.inputmethod.StartInputReason;
-import com.android.internal.inputmethod.UnbindReason;
-import com.android.internal.os.TransferPipe;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ConcurrentUtils;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.view.IInputMethodManager;
-import com.android.server.AccessibilityManagerInternal;
-import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
-import com.android.server.ServiceThread;
-import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-import com.android.server.input.InputManagerInternal;
-import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
-import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.utils.PriorityDump;
-import com.android.server.wm.WindowManagerInternal;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.security.InvalidParameterException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.OptionalInt;
-import java.util.WeakHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * This class provides a system service that manages input methods.
- *
- * @hide // CarInputMethodManagerService
- */
-public final class CarInputMethodManagerService extends IInputMethodManager.Stub
- implements Handler.Callback {
- // Virtual device id for test.
- private static final Integer VIRTUAL_STYLUS_ID_FOR_TEST = 999999;
- static final boolean DEBUG = false;
- static final String TAG = "CarInputMethodManagerService";
- public static final String PROTO_ARG = "--proto";
-
- @Retention(SOURCE)
- @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE})
- private @interface ShellCommandResult {
- int SUCCESS = 0;
- int FAILURE = -1;
- }
-
- private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
-
- private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
- private static final int MSG_REMOVE_IME_SURFACE = 1060;
- private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
- private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
-
- private static final int MSG_RESET_HANDWRITING = 1090;
- private static final int MSG_START_HANDWRITING = 1100;
- private static final int MSG_FINISH_HANDWRITING = 1110;
- private static final int MSG_REMOVE_HANDWRITING_WINDOW = 1120;
-
- private static final int MSG_PREPARE_HANDWRITING_DELEGATION = 1130;
-
- private static final int MSG_SET_INTERACTIVE = 3030;
-
- private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
-
- private static final int MSG_SYSTEM_UNLOCK_USER = 5000;
- private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010;
-
- private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
-
- private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
- private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
- private static final String HANDLER_THREAD_NAME = "android.imms";
-
- /**
- * When set, {@link #startInputUncheckedLocked} will return
- * {@link InputBindResult#NO_EDITOR} instead of starting an IME connection
- * unless {@link StartInputFlags#IS_TEXT_EDITOR} is set. This behavior overrides
- * {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE SOFT_INPUT_STATE_VISIBLE} and
- * {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE SOFT_INPUT_STATE_ALWAYS_VISIBLE}
- * starting from {@link android.os.Build.VERSION_CODES#P}.
- */
- private final boolean mPreventImeStartupUnlessTextEditor;
-
- /**
- * These IMEs are known not to behave well when evicted from memory and thus are exempt
- * from the IME startup avoidance behavior that is enabled by
- * {@link #mPreventImeStartupUnlessTextEditor}.
- */
- @NonNull
- private final String[] mNonPreemptibleInputMethods;
-
- @UserIdInt
- private int mLastSwitchUserId;
-
- final Context mContext;
- final Resources mRes;
- private final Handler mHandler;
- final InputMethodSettings mSettings;
- final SettingsObserver mSettingsObserver;
- private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
- new SparseBooleanArray(0);
- final WindowManagerInternal mWindowManagerInternal;
- private final ActivityManagerInternal mActivityManagerInternal;
- final PackageManagerInternal mPackageManagerInternal;
- final InputManagerInternal mInputManagerInternal;
- final ImePlatformCompatUtils mImePlatformCompatUtils;
- final InputMethodDeviceConfigs mInputMethodDeviceConfigs;
- private final DisplayManagerInternal mDisplayManagerInternal;
- private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
- new ArrayMap<>();
- private final UserManagerInternal mUserManagerInternal;
-
- // begin CarInputMethodManagerService
- private final CarInputMethodMenuController mMenuController;
- @NonNull private final CarInputMethodBindingController mBindingController;
- @NonNull private final AutofillController mAutofillController;
-
- @GuardedBy("ImfLock.class")
- @NonNull private final CarImeVisibilityStateComputer mVisibilityStateComputer;
-
- @GuardedBy("ImfLock.class")
- @NonNull private final CarDefaultImeVisibilityApplier mVisibilityApplier;
-
- private final LocalServiceImpl mImmi;
-
- private final ExecutorService mExecutor;
-
- private ImmsBroadcastReceiverForAllUsers mBroadcastReceiver;
- // end CarInputMethodManagerService
-
- /**
- * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
- *
- * <p>This field is used only within {@link #handleMessage(Message)} hence synchronization is
- * not necessary.</p>
- */
- @Nullable
- private AudioManagerInternal mAudioManagerInternal = null;
- @Nullable
- private VirtualDeviceManagerInternal mVdmInternal = null;
-
- // All known input methods.
- final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
- final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
- final InputMethodSubtypeSwitchingController mSwitchingController;
- final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
- new HardwareKeyboardShortcutController();
-
- /**
- * Tracks how many times {@link #mMethodMap} was updated.
- */
- @GuardedBy("ImfLock.class")
- private int mMethodMapUpdateCount = 0;
-
- /**
- * The display id for which the latest startInput was called.
- */
- @GuardedBy("ImfLock.class")
- int getDisplayIdToShowImeLocked() {
- return mDisplayIdToShowIme;
- }
-
- @GuardedBy("ImfLock.class")
- private int mDisplayIdToShowIme = INVALID_DISPLAY;
-
- @Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
- private boolean mShowOngoingImeSwitcherForPhones;
- @GuardedBy("ImfLock.class")
- private final HandwritingModeController mHwController;
- @GuardedBy("ImfLock.class")
- private IntArray mStylusIds;
-
- @GuardedBy("ImfLock.class")
- @Nullable
- private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
- @GuardedBy("ImfLock.class")
- @Nullable
- Future<?> mImeDrawsImeNavBarResLazyInitFuture;
-
- static class SessionState {
- final ClientState mClient;
- final IInputMethodInvoker mMethod;
-
- IInputMethodSession mSession;
- InputChannel mChannel;
-
- @Override
- public String toString() {
- return "SessionState{uid " + mClient.mUid + " pid " + mClient.mPid
- + " method " + Integer.toHexString(
- IInputMethodInvoker.getBinderIdentityHashCode(mMethod))
- + " session " + Integer.toHexString(
- System.identityHashCode(mSession))
- + " channel " + mChannel
- + "}";
- }
-
- SessionState(ClientState client, IInputMethodInvoker method,
- IInputMethodSession session, InputChannel channel) {
- mClient = client;
- mMethod = method;
- mSession = session;
- mChannel = channel;
- }
- }
-
- /**
- * Record session state for an accessibility service.
- */
- private static class AccessibilitySessionState {
- final ClientState mClient;
- // Id of the accessibility service.
- final int mId;
-
- public IAccessibilityInputMethodSession mSession;
-
- @Override
- public String toString() {
- return "AccessibilitySessionState{uid " + mClient.mUid + " pid " + mClient.mPid
- + " id " + Integer.toHexString(mId)
- + " session " + Integer.toHexString(
- System.identityHashCode(mSession))
- + "}";
- }
-
- AccessibilitySessionState(ClientState client, int id,
- IAccessibilityInputMethodSession session) {
- mClient = client;
- mId = id;
- mSession = session;
- }
- }
-
- private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
- private final CarInputMethodManagerService mImms;
- private final IInputMethodClient mClient;
-
- ClientDeathRecipient(CarInputMethodManagerService imms, IInputMethodClient client) {
- mImms = imms;
- mClient = client;
- }
-
- @Override
- public void binderDied() {
- mImms.removeClient(mClient);
- }
- }
-
- static final class ClientState {
- final IInputMethodClientInvoker mClient;
- final IRemoteInputConnection mFallbackInputConnection;
- final int mUid;
- final int mPid;
- final int mSelfReportedDisplayId;
- final InputBinding mBinding;
- final ClientDeathRecipient mClientDeathRecipient;
-
- boolean mSessionRequested;
- boolean mSessionRequestedForAccessibility;
- SessionState mCurSession;
- SparseArray<AccessibilitySessionState> mAccessibilitySessions = new SparseArray<>();
-
- @Override
- public String toString() {
- return "ClientState{" + Integer.toHexString(
- System.identityHashCode(this)) + " mUid=" + mUid
- + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
- }
-
- ClientState(IInputMethodClientInvoker client,
- IRemoteInputConnection fallbackInputConnection,
- int uid, int pid, int selfReportedDisplayId,
- ClientDeathRecipient clientDeathRecipient) {
- mClient = client;
- mFallbackInputConnection = fallbackInputConnection;
- mUid = uid;
- mPid = pid;
- mSelfReportedDisplayId = selfReportedDisplayId;
- mBinding = new InputBinding(null, mFallbackInputConnection.asBinder(), mUid, mPid);
- mClientDeathRecipient = clientDeathRecipient;
- }
- }
-
- @GuardedBy("ImfLock.class")
- final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
-
- private static final class VirtualDisplayInfo {
- /**
- * {@link ClientState} where {@link android.hardware.display.VirtualDisplay} is running.
- */
- private final ClientState mParentClient;
- /**
- * {@link Matrix} to convert screen coordinates in the embedded virtual display to
- * screen coordinates where {@link #mParentClient} exists.
- */
- private final Matrix mMatrix;
-
- VirtualDisplayInfo(ClientState parentClient, Matrix matrix) {
- mParentClient = parentClient;
- mMatrix = matrix;
- }
- }
-
- /**
- * A mapping table from virtual display IDs created for
- * {@link android.hardware.display.VirtualDisplay} to its parent IME client where the embedded
- * virtual display is running.
- *
- * <p>Note: this can be used only for virtual display IDs created by
- * {@link android.hardware.display.VirtualDisplay}.</p>
- */
- @GuardedBy("ImfLock.class")
- private final SparseArray<VirtualDisplayInfo> mVirtualDisplayIdToParentMap =
- new SparseArray<>();
-
- /**
- * Set once the system is ready to run third party code.
- */
- boolean mSystemReady;
-
- /**
- * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
- * This is to be synchronized with the secure settings keyed with
- * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
- *
- * <p>This can be transiently {@code null} when the system is re-initializing input method
- * settings, e.g., the system locale is just changed.</p>
- *
- * <p>Note that {@link InputMethodBindingController#getCurId()} is used to track which IME
- * is being connected to {@link CarInputMethodManagerService}.</p>
- *
- * @see InputMethodBindingController#getCurId()
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- String getSelectedMethodIdLocked() {
- return mBindingController.getSelectedMethodId();
- }
-
- @GuardedBy("ImfLock.class")
- private void setSelectedMethodIdLocked(@Nullable String selectedMethodId) {
- mBindingController.setSelectedMethodId(selectedMethodId);
- }
-
- /**
- * The current binding sequence number, incremented every time there is
- * a new bind performed.
- */
- @GuardedBy("ImfLock.class")
- private int getSequenceNumberLocked() {
- return mBindingController.getSequenceNumber();
- }
-
- /**
- * Increase the current binding sequence number by one.
- * Reset to 1 on overflow.
- */
- @GuardedBy("ImfLock.class")
- private void advanceSequenceNumberLocked() {
- mBindingController.advanceSequenceNumber();
- }
-
- /**
- * The client that is currently bound to an input method.
- */
- @Nullable
- private ClientState mCurClient;
-
- /**
- * The last window token that we confirmed to be focused. This is always updated upon reports
- * from the input method client. If the window state is already changed before the report is
- * handled, this field just keeps the last value.
- */
- IBinder mCurFocusedWindow;
-
- /**
- * The last window token that we confirmed that IME started talking to. This is always updated
- * upon reports from the input method. If the window state is already changed before the report
- * is handled, this field just keeps the last value.
- */
- IBinder mLastImeTargetWindow;
-
- /**
- * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
- *
- * @see #mCurFocusedWindow
- */
- @SoftInputModeFlags
- int mCurFocusedWindowSoftInputMode;
-
- /**
- * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
- * IME-focusable window gained focus (without necessarily starting an input connection),
- * while {@link #mCurClient} only gets updated when we actually start an input connection.
- *
- * @see #mCurFocusedWindow
- */
- @Nullable
- ClientState mCurFocusedWindowClient;
-
- /**
- * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
- * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
- * from {@link #mCurClient}.
- *
- * @see #mCurFocusedWindow
- */
- @Nullable
- EditorInfo mCurFocusedWindowEditorInfo;
-
- /**
- * The {@link IRemoteInputConnection} last provided by the current client.
- */
- IRemoteInputConnection mCurInputConnection;
-
- /**
- * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
- * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
- */
- ImeOnBackInvokedDispatcher mCurImeDispatcher;
-
- /**
- * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
- */
- @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
-
- /**
- * The {@link EditorInfo} last provided by the current client.
- */
- @Nullable
- EditorInfo mCurEditorInfo;
-
- /**
- * A special {@link Matrix} to convert virtual screen coordinates to the IME target display
- * coordinates.
- *
- * <p>Used only while the IME client is running in a virtual display. {@code null}
- * otherwise.</p>
- */
- @Nullable
- private Matrix mCurVirtualDisplayToScreenMatrix = null;
-
- /**
- * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
- * connected to or in the process of connecting to.
- *
- * <p>This can be {@code null} when no input method is connected.</p>
- *
- * @see #getSelectedMethodIdLocked()
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- private String getCurIdLocked() {
- return mBindingController.getCurId();
- }
-
- /**
- * The current subtype of the current input method.
- */
- private InputMethodSubtype mCurrentSubtype;
-
- /**
- * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}
- */
- private boolean mCurPerceptible;
-
- /**
- * Set to true if our ServiceConnection is currently actively bound to
- * a service (whether or not we have gotten its IBinder back yet).
- */
- @GuardedBy("ImfLock.class")
- private boolean hasConnectionLocked() {
- return mBindingController.hasConnection();
- }
-
- /** The token tracking the current IME request or {@code null} otherwise. */
- @Nullable
- private ImeTracker.Token mCurStatsToken;
-
- /**
- * {@code true} if the current input method is in fullscreen mode.
- */
- boolean mInFullscreenMode;
-
- /**
- * The Intent used to connect to the current input method.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- private Intent getCurIntentLocked() {
- return mBindingController.getCurIntent();
- }
-
- /**
- * The token we have made for the currently active input method, to
- * identify it in the future.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- IBinder getCurTokenLocked() {
- return mBindingController.getCurToken();
- }
-
- /**
- * The displayId of current active input method.
- */
- @GuardedBy("ImfLock.class")
- int getCurTokenDisplayIdLocked() {
- return mCurTokenDisplayId;
- }
-
- @GuardedBy("ImfLock.class")
- void setCurTokenDisplayIdLocked(int curTokenDisplayId) {
- mCurTokenDisplayId = curTokenDisplayId;
- }
-
- @GuardedBy("ImfLock.class")
- private int mCurTokenDisplayId = INVALID_DISPLAY;
-
- /**
- * The host input token of the current active input method.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- private IBinder mCurHostInputToken;
-
- /**
- * The display ID of the input method indicates the fallback display which returned by
- * {@link #computeImeDisplayIdForTarget}.
- */
- static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY;
-
- /**
- * If non-null, this is the input method service we are currently connected
- * to.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- IInputMethodInvoker getCurMethodLocked() {
- return mBindingController.getCurMethod();
- }
-
- /**
- * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntentLocked()}.
- */
- @GuardedBy("ImfLock.class")
- private int getCurMethodUidLocked() {
- return mBindingController.getCurMethodUid();
- }
-
- /**
- * Time that we last initiated a bind to the input method, to determine
- * if we should try to disconnect and reconnect to it.
- */
- @GuardedBy("ImfLock.class")
- private long getLastBindTimeLocked() {
- return mBindingController.getLastBindTime();
- }
-
- /**
- * Have we called mCurMethod.bindInput()?
- */
- boolean mBoundToMethod;
-
- /**
- * Have we called bindInput() for accessibility services?
- */
- boolean mBoundToAccessibility;
-
- /**
- * Currently enabled session.
- */
- @GuardedBy("ImfLock.class")
- SessionState mEnabledSession;
- SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>();
-
- /**
- * True if the device is currently interactive with user. The value is true initially.
- */
- boolean mIsInteractive = true;
-
- int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
-
- /**
- * A set of status bits regarding the active IME.
- *
- * <p>This value is a combination of following two bits:</p>
- * <dl>
- * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
- * <dd>
- * If this bit is ON, connected IME is ready to accept touch/key events.
- * </dd>
- * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
- * <dd>
- * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
- * </dd>
- * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
- * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
- * currently invisible.
- * </dd>
- * </dl>
- * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
- * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
- */
- int mImeWindowVis;
-
- private LocaleList mLastSystemLocales;
- private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
- private final String mSlotIme;
-
- /**
- * Registered {@link InputMethodListListener}.
- * This variable can be accessed from both of MainThread and BinderThread.
- */
- private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
- new CopyOnWriteArrayList<>();
-
- /**
- * Internal state snapshot when
- * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
- *
- * <p>Calling that IPC endpoint basically means that
- * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
- * back in the current IME process shortly, which will also affect what the current IME starts
- * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
- * snapshot will be taken every time when {@link CarInputMethodManagerService} is initiating a
- * new logical input session between the client application and the current IME.</p>
- *
- * <p>Be careful to not keep strong references to this object forever, which can prevent
- * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
- * </p>
- */
- private static class StartInputInfo {
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- final int mSequenceNumber;
- final long mTimestamp;
- final long mWallTime;
- @UserIdInt
- final int mImeUserId;
- @NonNull
- final IBinder mImeToken;
- final int mImeDisplayId;
- @NonNull
- final String mImeId;
- @StartInputReason
- final int mStartInputReason;
- final boolean mRestarting;
- @UserIdInt
- final int mTargetUserId;
- final int mTargetDisplayId;
- @Nullable
- final IBinder mTargetWindow;
- @NonNull
- final EditorInfo mEditorInfo;
- @SoftInputModeFlags
- final int mTargetWindowSoftInputMode;
- final int mClientBindSequenceNumber;
-
- StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
- @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
- @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
- @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
- int clientBindSequenceNumber) {
- mSequenceNumber = sSequenceNumber.getAndIncrement();
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mImeUserId = imeUserId;
- mImeToken = imeToken;
- mImeDisplayId = imeDisplayId;
- mImeId = imeId;
- mStartInputReason = startInputReason;
- mRestarting = restarting;
- mTargetUserId = targetUserId;
- mTargetDisplayId = targetDisplayId;
- mTargetWindow = targetWindow;
- mEditorInfo = editorInfo;
- mTargetWindowSoftInputMode = targetWindowSoftInputMode;
- mClientBindSequenceNumber = clientBindSequenceNumber;
- }
- }
-
- @GuardedBy("ImfLock.class")
- private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
-
- @VisibleForTesting
- static final class SoftInputShowHideHistory {
- private final Entry[] mEntries = new Entry[16];
- private int mNextIndex = 0;
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- static final class Entry {
- final int mSequenceNumber = sSequenceNumber.getAndIncrement();
- @Nullable
- final ClientState mClientState;
- @SoftInputModeFlags
- final int mFocusedWindowSoftInputMode;
- @SoftInputShowHideReason
- final int mReason;
- // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
- final long mTimestamp;
- final long mWallTime;
- final boolean mInFullscreenMode;
- @NonNull
- final String mFocusedWindowName;
- @Nullable
- final EditorInfo mEditorInfo;
- @NonNull
- final String mRequestWindowName;
- @Nullable
- final String mImeControlTargetName;
- @Nullable
- final String mImeTargetNameFromWm;
- @Nullable
- final String mImeSurfaceParentName;
-
- Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
- @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
- boolean inFullscreenMode, String requestWindowName,
- @Nullable String imeControlTargetName, @Nullable String imeTargetName,
- @Nullable String imeSurfaceParentName) {
- mClientState = client;
- mEditorInfo = editorInfo;
- mFocusedWindowName = focusedWindowName;
- mFocusedWindowSoftInputMode = softInputMode;
- mReason = reason;
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mInFullscreenMode = inFullscreenMode;
- mRequestWindowName = requestWindowName;
- mImeControlTargetName = imeControlTargetName;
- mImeTargetNameFromWm = imeTargetName;
- mImeSurfaceParentName = imeSurfaceParentName;
- }
- }
-
- void addEntry(@NonNull Entry entry) {
- final int index = mNextIndex;
- mEntries[index] = entry;
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("SoftInputShowHideHistory #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")");
-
- pw.print(prefix);
- pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
- entry.mReason));
- pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
-
- pw.print(prefix);
- pw.println(" requestClient=" + entry.mClientState);
-
- pw.print(prefix);
- pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
-
- pw.print(prefix);
- pw.println(" requestWindowName=" + entry.mRequestWindowName);
-
- pw.print(prefix);
- pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
-
- pw.print(prefix);
- pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
-
- pw.print(prefix);
- pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
-
- pw.print(prefix);
- pw.print(" editorInfo: ");
- if (entry.mEditorInfo != null) {
- pw.print(" inputType=" + entry.mEditorInfo.inputType);
- pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
- pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
- } else {
- pw.println("null");
- }
-
- pw.print(prefix);
- pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mFocusedWindowSoftInputMode));
- }
- }
- }
-
- /**
- * A ring buffer to store the history of {@link StartInputInfo}.
- */
- private static final class StartInputHistory {
- /**
- * Entry size for non low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
-
- /**
- * Entry size for low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
-
- private static int getEntrySize() {
- if (ActivityManager.isLowRamDeviceStatic()) {
- return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
- } else {
- return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
- }
- }
-
- /**
- * Backing store for the ring buffer.
- */
- private final Entry[] mEntries = new Entry[getEntrySize()];
-
- /**
- * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
- * write.
- */
- private int mNextIndex = 0;
-
- /**
- * Recyclable entry to store the information in {@link StartInputInfo}.
- */
- private static final class Entry {
- int mSequenceNumber;
- long mTimestamp;
- long mWallTime;
- @UserIdInt
- int mImeUserId;
- @NonNull
- String mImeTokenString;
- int mImeDisplayId;
- @NonNull
- String mImeId;
- @StartInputReason
- int mStartInputReason;
- boolean mRestarting;
- @UserIdInt
- int mTargetUserId;
- int mTargetDisplayId;
- @NonNull
- String mTargetWindowString;
- @NonNull
- EditorInfo mEditorInfo;
- @SoftInputModeFlags
- int mTargetWindowSoftInputMode;
- int mClientBindSequenceNumber;
-
- Entry(@NonNull StartInputInfo original) {
- set(original);
- }
-
- void set(@NonNull StartInputInfo original) {
- mSequenceNumber = original.mSequenceNumber;
- mTimestamp = original.mTimestamp;
- mWallTime = original.mWallTime;
- mImeUserId = original.mImeUserId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mImeTokenString = String.valueOf(original.mImeToken);
- mImeDisplayId = original.mImeDisplayId;
- mImeId = original.mImeId;
- mStartInputReason = original.mStartInputReason;
- mRestarting = original.mRestarting;
- mTargetUserId = original.mTargetUserId;
- mTargetDisplayId = original.mTargetDisplayId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mTargetWindowString = String.valueOf(original.mTargetWindow);
- mEditorInfo = original.mEditorInfo;
- mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
- mClientBindSequenceNumber = original.mClientBindSequenceNumber;
- }
- }
-
- /**
- * Add a new entry and discard the oldest entry as needed.
- * @param info {@link StartInputInfo} to be added.
- */
- void addEntry(@NonNull StartInputInfo info) {
- final int index = mNextIndex;
- if (mEntries[index] == null) {
- mEntries[index] = new Entry(info);
- } else {
- mEntries[index].set(info);
- }
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("StartInput #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")"
- + " reason="
- + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
- + " restarting=" + entry.mRestarting);
-
- pw.print(prefix);
- pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
- pw.print(" imeUserId=" + entry.mImeUserId);
- pw.println(" imeDisplayId=" + entry.mImeDisplayId);
-
- pw.print(prefix);
- pw.println(" targetWin=" + entry.mTargetWindowString
- + " [" + entry.mEditorInfo.packageName + "]"
- + " targetUserId=" + entry.mTargetUserId
- + " targetDisplayId=" + entry.mTargetDisplayId
- + " clientBindSeq=" + entry.mClientBindSequenceNumber);
-
- pw.print(prefix);
- pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mTargetWindowSoftInputMode));
-
- pw.print(prefix);
- pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
- + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
- + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
- + " fieldName=" + entry.mEditorInfo.fieldName
- + " actionId=" + entry.mEditorInfo.actionId
- + " actionLabel=" + entry.mEditorInfo.actionLabel);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- @NonNull
- private final StartInputHistory mStartInputHistory = new StartInputHistory();
-
- @GuardedBy("ImfLock.class")
- @NonNull
- private final SoftInputShowHideHistory mSoftInputShowHideHistory =
- new SoftInputShowHideHistory();
-
- @NonNull
- private final ImeTrackerService mImeTrackerService;
-
- class SettingsObserver extends ContentObserver {
- int mUserId;
- boolean mRegistered = false;
- @NonNull
- String mLastEnabled = "";
-
- /**
- * <em>This constructor must be called within the lock.</em>
- */
- SettingsObserver(Handler handler) {
- super(handler);
- }
-
- @GuardedBy("ImfLock.class")
- public void registerContentObserverLocked(@UserIdInt int userId) {
- if (mRegistered && mUserId == userId) {
- return;
- }
- ContentResolver resolver = mContext.getContentResolver();
- if (mRegistered) {
- mContext.getContentResolver().unregisterContentObserver(this);
- mRegistered = false;
- }
- if (mUserId != userId) {
- mLastEnabled = "";
- mUserId = userId;
- }
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.DEFAULT_INPUT_METHOD), false, this, userId);
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_INPUT_METHODS), false, this, userId);
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
- mRegistered = true;
- }
-
- @Override public void onChange(boolean selfChange, Uri uri) {
- final Uri showImeUri = Settings.Secure.getUriFor(
- Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
- final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
- synchronized (ImfLock.class) {
- if (showImeUri.equals(uri)) {
- mMenuController.updateKeyboardFromSettingsLocked();
- } else if (accessibilityRequestingNoImeUri.equals(uri)) {
- final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId);
- mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
- accessibilitySoftKeyboardSetting);
- if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
- 0 /* flags */, null /* resultReceiver */,
- SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
- } else if (isShowRequestedForCurrentWindow()) {
- showCurrentInputImplicitLocked(mCurFocusedWindow,
- SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
- }
- } else {
- boolean enabledChanged = false;
- String newEnabled = mSettings.getEnabledInputMethodsStr();
- if (!mLastEnabled.equals(newEnabled)) {
- mLastEnabled = newEnabled;
- enabledChanged = true;
- }
- updateInputMethodsFromSettingsLocked(enabledChanged);
- }
- }
- }
-
- @Override
- public String toString() {
- return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered
- + " mLastEnabled=" + mLastEnabled + "}";
- }
- }
-
- /**
- * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to all the users.
- */
- private final class ImmsBroadcastReceiverForAllUsers extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
- final PendingResult pendingResult = getPendingResult();
- if (pendingResult == null) {
- return;
- }
- // sender userId can be a real user ID or USER_ALL.
- final int senderUserId = pendingResult.getSendingUserId();
- if (senderUserId != UserHandle.USER_ALL) {
- if (senderUserId != mSettings.getCurrentUserId()) {
- // A background user is trying to hide the dialog. Ignore.
- return;
- }
- }
- mMenuController.hideInputMethodMenu();
- } else {
- Slog.w(TAG, "Unexpected intent " + intent);
- }
- }
- }
-
- /**
- * Handles {@link Intent#ACTION_LOCALE_CHANGED}.
- *
- * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
- * the users. We should ignore this event if this is about any background user's locale.</p>
- *
- * <p>Caution: This method must not be called when system is not ready.</p>
- */
- void onActionLocaleChanged() {
- synchronized (ImfLock.class) {
- final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales();
- if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) {
- return;
- }
- buildInputMethodListLocked(true);
- // If the locale is changed, needs to reset the default ime
- resetDefaultImeLocked(mContext);
- updateFromSettingsLocked(true);
- mLastSystemLocales = possibleNewLocale;
- }
- }
-
- final class MyPackageMonitor extends PackageMonitor {
- /**
- * Package names that are known to contain {@link InputMethodService}.
- *
- * <p>No need to include packages because of direct-boot unaware IMEs since we always rescan
- * all the packages when the user is unlocked, and direct-boot awareness will not be changed
- * dynamically unless the entire package is updated, which also always triggers package
- * rescanning.</p>
- */
- @GuardedBy("ImfLock.class")
- private final ArraySet<String> mKnownImePackageNames = new ArraySet<>();
-
- /**
- * Packages that are appeared, disappeared, or modified for whatever reason.
- *
- * <p>Note: For now we intentionally use {@link ArrayList} instead of {@link ArraySet}
- * because 1) the number of elements is almost always 1 or so, and 2) we do not care
- * duplicate elements for our use case.</p>
- *
- * <p>This object must be accessed only from callback methods in {@link PackageMonitor},
- * which should be bound to {@link #getRegisteredHandler()}.</p>
- */
- private final ArrayList<String> mChangedPackages = new ArrayList<>();
-
- /**
- * {@code true} if one or more packages that contain {@link InputMethodService} appeared.
- *
- * <p>This field must be accessed only from callback methods in {@link PackageMonitor},
- * which should be bound to {@link #getRegisteredHandler()}.</p>
- */
- private boolean mImePackageAppeared = false;
-
- @GuardedBy("ImfLock.class")
- void clearKnownImePackageNamesLocked() {
- mKnownImePackageNames.clear();
- }
-
- @GuardedBy("ImfLock.class")
- void addKnownImePackageNameLocked(@NonNull String packageName) {
- mKnownImePackageNames.add(packageName);
- }
-
- @GuardedBy("ImfLock.class")
- private boolean isChangingPackagesOfCurrentUserLocked() {
- final int userId = getChangingUserId();
- final boolean retval = userId == mSettings.getCurrentUserId();
- if (DEBUG) {
- if (!retval) {
- Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
- }
- }
- return retval;
- }
-
- @Override
- public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
- synchronized (ImfLock.class) {
- if (!isChangingPackagesOfCurrentUserLocked()) {
- return false;
- }
- String curInputMethodId = mSettings.getSelectedInputMethod();
- final int numImes = mMethodList.size();
- if (curInputMethodId != null) {
- for (int i = 0; i < numImes; i++) {
- InputMethodInfo imi = mMethodList.get(i);
- if (imi.getId().equals(curInputMethodId)) {
- for (String pkg : packages) {
- if (imi.getPackageName().equals(pkg)) {
- if (!doit) {
- return true;
- }
- resetSelectedInputMethodAndSubtypeLocked("");
- chooseNewDefaultIMELocked();
- return true;
- }
- }
- }
- }
- }
- }
- return false;
- }
-
- @Override
- public void onBeginPackageChanges() {
- clearPackageChangeState();
- }
-
- @Override
- public void onPackageAppeared(String packageName, int reason) {
- if (!mImePackageAppeared) {
- final PackageManager pm = mContext.getPackageManager();
- final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
- new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName),
- PackageManager.MATCH_DISABLED_COMPONENTS, getChangingUserId());
- // No need to lock this because we access it only on getRegisteredHandler().
- if (!services.isEmpty()) {
- mImePackageAppeared = true;
- }
- }
- // No need to lock this because we access it only on getRegisteredHandler().
- mChangedPackages.add(packageName);
- }
-
- @Override
- public void onPackageDisappeared(String packageName, int reason) {
- // No need to lock this because we access it only on getRegisteredHandler().
- mChangedPackages.add(packageName);
- }
-
- @Override
- public void onPackageModified(String packageName) {
- // No need to lock this because we access it only on getRegisteredHandler().
- mChangedPackages.add(packageName);
- }
-
- @Override
- public void onPackagesSuspended(String[] packages) {
- // No need to lock this because we access it only on getRegisteredHandler().
- for (String packageName : packages) {
- mChangedPackages.add(packageName);
- }
- }
-
- @Override
- public void onPackagesUnsuspended(String[] packages) {
- // No need to lock this because we access it only on getRegisteredHandler().
- for (String packageName : packages) {
- mChangedPackages.add(packageName);
- }
- }
-
- @Override
- public void onPackageDataCleared(String packageName, int uid) {
- boolean changed = false;
- for (InputMethodInfo imi : mMethodList) {
- if (imi.getPackageName().equals(packageName)) {
- mAdditionalSubtypeMap.remove(imi.getId());
- changed = true;
- }
- }
- if (changed) {
- AdditionalSubtypeUtils.save(
- mAdditionalSubtypeMap, mMethodMap, mSettings.getCurrentUserId());
- mChangedPackages.add(packageName);
- }
- }
-
- @Override
- public void onFinishPackageChanges() {
- onFinishPackageChangesInternal();
- clearPackageChangeState();
- }
-
- @Override
- public void onUidRemoved(int uid) {
- synchronized (ImfLock.class) {
- mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid);
- }
- }
-
- private void clearPackageChangeState() {
- // No need to lock them because we access these fields only on getRegisteredHandler().
- mChangedPackages.clear();
- mImePackageAppeared = false;
- }
-
- @GuardedBy("ImfLock.class")
- private boolean shouldRebuildInputMethodListLocked() {
- // This method is guaranteed to be called only by getRegisteredHandler().
-
- // If there is any new package that contains at least one IME, then rebuilt the list
- // of IMEs.
- if (mImePackageAppeared) {
- return true;
- }
-
- // Otherwise, check if mKnownImePackageNames and mChangedPackages have any intersection.
- // TODO: Consider to create a utility method to do the following test. List.retainAll()
- // is an option, but it may still do some extra operations that we do not need here.
- final int numPackages = mChangedPackages.size();
- for (int i = 0; i < numPackages; ++i) {
- final String packageName = mChangedPackages.get(i);
- if (mKnownImePackageNames.contains(packageName)) {
- return true;
- }
- }
- return false;
- }
-
- private void onFinishPackageChangesInternal() {
- synchronized (ImfLock.class) {
- if (!isChangingPackagesOfCurrentUserLocked()) {
- return;
- }
- if (!shouldRebuildInputMethodListLocked()) {
- return;
- }
-
- InputMethodInfo curIm = null;
- String curInputMethodId = mSettings.getSelectedInputMethod();
- final int numImes = mMethodList.size();
- if (curInputMethodId != null) {
- for (int i = 0; i < numImes; i++) {
- InputMethodInfo imi = mMethodList.get(i);
- final String imiId = imi.getId();
- if (imiId.equals(curInputMethodId)) {
- curIm = imi;
- }
-
- int change = isPackageDisappearing(imi.getPackageName());
- if (change == PACKAGE_TEMPORARY_CHANGE
- || change == PACKAGE_PERMANENT_CHANGE) {
- Slog.i(TAG, "Input method uninstalled, disabling: "
- + imi.getComponent());
- setInputMethodEnabledLocked(imi.getId(), false);
- } else if (change == PACKAGE_UPDATING) {
- Slog.i(TAG,
- "Input method reinstalling, clearing additional subtypes: "
- + imi.getComponent());
- mAdditionalSubtypeMap.remove(imi.getId());
- AdditionalSubtypeUtils.save(mAdditionalSubtypeMap,
- mMethodMap,
- mSettings.getCurrentUserId());
- }
- }
- }
-
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
-
- boolean changed = false;
-
- if (curIm != null) {
- int change = isPackageDisappearing(curIm.getPackageName());
- if (change == PACKAGE_TEMPORARY_CHANGE
- || change == PACKAGE_PERMANENT_CHANGE) {
- final PackageManager userAwarePackageManager =
- getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
- ServiceInfo si = null;
- try {
- si = userAwarePackageManager.getServiceInfo(curIm.getComponent(),
- PackageManager.ComponentInfoFlags.of(0));
- } catch (PackageManager.NameNotFoundException ignored) {
- }
- if (si == null) {
- // Uh oh, current input method is no longer around!
- // Pick another one...
- Slog.i(TAG, "Current input method removed: " + curInputMethodId);
- updateSystemUiLocked(0 /* vis */, mBackDisposition);
- if (!chooseNewDefaultIMELocked()) {
- changed = true;
- curIm = null;
- Slog.i(TAG, "Unsetting current input method");
- resetSelectedInputMethodAndSubtypeLocked("");
- }
- }
- }
- }
-
- if (curIm == null) {
- // We currently don't have a default input method... is
- // one now available?
- changed = chooseNewDefaultIMELocked();
- } else if (!changed && isPackageModified(curIm.getPackageName())) {
- // Even if the current input method is still available, mCurrentSubtype could
- // be obsolete when the package is modified in practice.
- changed = true;
- }
-
- if (changed) {
- updateFromSettingsLocked(false);
- }
- }
- }
- }
-
- private static final class UserSwitchHandlerTask implements Runnable {
- final CarInputMethodManagerService mService;
-
- @UserIdInt
- final int mToUserId;
-
- @Nullable
- IInputMethodClientInvoker mClientToBeReset;
-
- UserSwitchHandlerTask(CarInputMethodManagerService service, @UserIdInt int toUserId,
- @Nullable IInputMethodClientInvoker clientToBeReset) {
- mService = service;
- mToUserId = toUserId;
- mClientToBeReset = clientToBeReset;
- }
-
- @Override
- public void run() {
- synchronized (ImfLock.class) {
- if (mService.mUserSwitchHandlerTask != this) {
- // This task was already canceled before it is handled here. So do nothing.
- return;
- }
- mService.switchUserOnHandlerLocked(mService.mUserSwitchHandlerTask.mToUserId,
- mClientToBeReset);
- mService.mUserSwitchHandlerTask = null;
- }
- }
- }
-
- /**
- * When non-{@code null}, this represents pending user-switch task, which is to be executed as
- * a handler callback. This needs to be set and unset only within the lock.
- */
- @Nullable
- @GuardedBy("ImfLock.class")
- private UserSwitchHandlerTask mUserSwitchHandlerTask;
-
- void onUnlockUser(@UserIdInt int userId) {
- synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getCurrentUserId();
- if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
- }
- if (userId != currentUserId) {
- return;
- }
- mSettings.switchCurrentUser(currentUserId, !mSystemReady);
- if (mSystemReady) {
- // We need to rebuild IMEs.
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- void scheduleSwitchUserTaskLocked(@UserIdInt int userId,
- @Nullable IInputMethodClientInvoker clientToBeReset) {
- if (mUserSwitchHandlerTask != null) {
- if (mUserSwitchHandlerTask.mToUserId == userId) {
- mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset;
- return;
- }
- mHandler.removeCallbacks(mUserSwitchHandlerTask);
- }
- // Hide soft input before user switch task since switch task may block main handler a while
- // and delayed the hideCurrentInputLocked().
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
- null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER);
- final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
- clientToBeReset);
- mUserSwitchHandlerTask = task;
- mHandler.post(task);
- }
-
- public CarInputMethodManagerService(Context context, ExecutorService executor) {
- this(context, null, null, executor);
- }
-
- @VisibleForTesting
- CarInputMethodManagerService(
- Context context,
- @Nullable ServiceThread serviceThreadForTesting,
- @Nullable CarInputMethodBindingController bindingControllerForTesting,
- ExecutorService executor) {
- mContext = context;
- mRes = context.getResources();
- // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
- // additional subtypes in switchUserOnHandlerLocked().
- final ServiceThread thread =
- serviceThreadForTesting != null
- ? serviceThreadForTesting
- : new ServiceThread(
- HANDLER_THREAD_NAME,
- Process.THREAD_PRIORITY_FOREGROUND,
- true /* allowIo */);
- thread.start();
- mHandler = Handler.createAsync(thread.getLooper(), this);
- mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
- ? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
- // Note: SettingsObserver doesn't register observers in its constructor.
- mSettingsObserver = new SettingsObserver(mHandler);
- mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
- mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
- mImePlatformCompatUtils = new ImePlatformCompatUtils();
- mInputMethodDeviceConfigs = new InputMethodDeviceConfigs();
- mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
- mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
-
- mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
-
- mShowOngoingImeSwitcherForPhones = false;
-
- final int userId = mActivityManagerInternal.getCurrentUserId();
-
- mLastSwitchUserId = userId;
-
- // mSettings should be created before buildInputMethodListLocked
- mSettings = new InputMethodSettings(mContext, mMethodMap, userId, !mSystemReady);
-
- updateCurrentProfileIds();
- AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
- mSwitchingController =
- InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
- mHardwareKeyboardShortcutController.reset(mSettings);
- mMenuController = new CarInputMethodMenuController(this);
- mBindingController =
- bindingControllerForTesting != null
- ? bindingControllerForTesting
- : new CarInputMethodBindingController(this);
-
- // CarInputMethodManagerService
- mAutofillController = userId == UserHandle.USER_SYSTEM
- ? new NullAutofillSuggestionsController() :
- new CarAutofillSuggestionsController(this);
-
- mVisibilityStateComputer = new CarImeVisibilityStateComputer(this);
- mVisibilityApplier = new CarDefaultImeVisibilityApplier(this);
-
- mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
- com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
- mNonPreemptibleInputMethods = mRes.getStringArray(
- com.android.internal.R.array.config_nonPreemptibleInputMethods);
- mHwController = new HandwritingModeController(thread.getLooper(),
- new InkWindowInitializer());
- registerDeviceListenerAndCheckStylusSupport();
- mImmi = new LocalServiceImpl(); // CarInputMethodManagerService
- mExecutor = executor; // CarInputMethodManagerService
- }
-
- // CarInputMethodManagerService
- InputMethodManagerInternal getInputMethodManagerInternal() {
- return mImmi;
- }
-
- // CarInputMethodManagerService
- AutofillController getAutofillController() {
- return mAutofillController;
- }
-
- private final class InkWindowInitializer implements Runnable {
- public void run() {
- synchronized (ImfLock.class) {
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null) {
- curMethod.initInkWindow();
- }
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void resetDefaultImeLocked(Context context) {
- // Do not reset the default (current) IME when it is a 3rd-party IME
- String selectedMethodId = getSelectedMethodIdLocked();
- if (selectedMethodId != null && !mMethodMap.get(selectedMethodId).isSystem()) {
- return;
- }
- final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
- context, mSettings.getEnabledInputMethodListLocked());
- if (suitableImes.isEmpty()) {
- Slog.i(TAG, "No default found");
- return;
- }
- final InputMethodInfo defIm = suitableImes.get(0);
- if (DEBUG) {
- Slog.i(TAG, "Default found, using " + defIm.getId());
- }
- setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
- }
-
- @GuardedBy("ImfLock.class")
- private void maybeInitImeNavbarConfigLocked(@UserIdInt int targetUserId) {
- // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the
- // profile parent user.
- // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups.
- final int profileParentUserId = mUserManagerInternal.getProfileParentId(targetUserId);
- if (mImeDrawsImeNavBarRes != null
- && mImeDrawsImeNavBarRes.getUserId() != profileParentUserId) {
- mImeDrawsImeNavBarRes.close();
- mImeDrawsImeNavBarRes = null;
- }
- if (mImeDrawsImeNavBarRes == null) {
- final Context userContext;
- if (mContext.getUserId() == profileParentUserId) {
- userContext = mContext;
- } else {
- userContext = mContext.createContextAsUser(UserHandle.of(profileParentUserId),
- 0 /* flags */);
- }
- mImeDrawsImeNavBarRes = OverlayableSystemBooleanResourceWrapper.create(userContext,
- com.android.internal.R.bool.config_imeDrawsImeNavBar, mHandler, resource -> {
- synchronized (ImfLock.class) {
- if (resource == mImeDrawsImeNavBarRes) {
- sendOnNavButtonFlagsChangedLocked();
- }
- }
- });
- }
- }
-
- @NonNull
- private static PackageManager getPackageManagerForUser(@NonNull Context context,
- @UserIdInt int userId) {
- return context.getUserId() == userId
- ? context.getPackageManager()
- : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */)
- .getPackageManager();
- }
-
- @GuardedBy("ImfLock.class")
- private void switchUserOnHandlerLocked(@UserIdInt int newUserId,
- IInputMethodClientInvoker clientToBeReset) {
- if (DEBUG) {
- Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
- + " currentUserId=" + mSettings.getCurrentUserId());
- }
-
- maybeInitImeNavbarConfigLocked(newUserId);
-
- // ContentObserver should be registered again when the user is changed
- mSettingsObserver.registerContentObserverLocked(newUserId);
-
- // If the system is not ready or the device is not yed unlocked by the user, then we use
- // copy-on-write settings.
- final boolean useCopyOnWriteSettings =
- !mSystemReady || !mUserManagerInternal.isUserUnlockingOrUnlocked(newUserId);
- mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
- updateCurrentProfileIds();
- // Additional subtypes should be reset when the user is changed
- AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId);
- final String defaultImiId = mSettings.getSelectedInputMethod();
-
- if (DEBUG) {
- Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
- + " defaultImiId=" + defaultImiId);
- }
-
- // For secondary users, the list of enabled IMEs may not have been updated since the
- // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
- // not be empty even if the IME has been uninstalled by the primary user.
- // Even in such cases, IMMS works fine because it will find the most applicable
- // IME for that user.
- final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
- mLastSystemLocales = mRes.getConfiguration().getLocales();
-
- // The mSystemReady flag is set during boot phase,
- // and user switch would not happen at that time.
- resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- buildInputMethodListLocked(initialUserSwitch);
- if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
- // This is the first time of the user switch and
- // set the current ime to the proper one.
- resetDefaultImeLocked(mContext);
- }
- updateFromSettingsLocked(true);
-
- if (initialUserSwitch) {
- InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, newUserId),
- mSettings.getEnabledInputMethodListLocked());
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
- + " selectedIme=" + mSettings.getSelectedInputMethod());
- }
-
- mLastSwitchUserId = newUserId;
-
- if (mIsInteractive && clientToBeReset != null) {
- final ClientState cs = mClients.get(clientToBeReset.asBinder());
- if (cs == null) {
- // The client is already gone.
- return;
- }
- cs.mClient.scheduleStartInputIfNecessary(mInFullscreenMode);
- }
- }
-
- void updateCurrentProfileIds() {
- mSettings.setCurrentProfileIds(
- mUserManagerInternal.getProfileIds(mSettings.getCurrentUserId(),
- false /* enabledOnly */));
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- try {
- return super.onTransact(code, data, reply, flags);
- } catch (RuntimeException e) {
- // The input method manager only throws security exceptions, so let's
- // log all others.
- if (!(e instanceof SecurityException)) {
- Slog.wtf(TAG, "Input Method Manager Crash", e);
- }
- throw e;
- }
- }
-
- /**
- * TODO(b/32343335): The entire systemRunning() method needs to be revisited.
- */
- public void systemRunning() {
- synchronized (ImfLock.class) {
- if (DEBUG) {
- Slog.d(TAG, "--- systemReady");
- }
- if (!mSystemReady) {
- mSystemReady = true;
- mLastSystemLocales = mRes.getConfiguration().getLocales();
- final int currentUserId = mSettings.getCurrentUserId();
- mSettings.switchCurrentUser(currentUserId,
- !mUserManagerInternal.isUserUnlockingOrUnlocked(currentUserId));
- mStatusBarManagerInternal =
- LocalServices.getService(StatusBarManagerInternal.class);
- hideStatusBarIconLocked();
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
- mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
- com.android.internal.R.bool.show_ongoing_ime_switcher);
- if (mShowOngoingImeSwitcherForPhones) {
- mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(available -> {
- mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
- available ? 1 : 0, 0 /* unused */).sendToTarget();
- });
- }
-
- // begin CarInputMethodManagerService
- mImeDrawsImeNavBarResLazyInitFuture = mExecutor.submit(() -> {
- // Note that the synchronization block below guarantees that the task
- // can never be completed before the returned Future<?> object is assigned to
- // the "mImeDrawsImeNavBarResLazyInitFuture" field.
- synchronized (ImfLock.class) {
- mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mSettings.getCurrentUserId()) {
- // This means that the current user is already switched to other user
- // before the background task is executed. In this scenario the relevant
- // field should already be initialized.
- return;
- }
- maybeInitImeNavbarConfigLocked(currentUserId);
- }
- });
- // end CarInputMethodManagerService
-
- mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
- mSettingsObserver.registerContentObserverLocked(currentUserId);
-
- // begin CarInputMethodManagerService
- // ImmsBroadcastReceiverForSystemUser moved to IMMS Proxy
- // end CarInputMethodManagerService
-
- final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
- broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mBroadcastReceiver = new ImmsBroadcastReceiverForAllUsers();
- mContext.registerReceiverAsUser(mBroadcastReceiver,
- UserHandle.ALL, broadcastFilterForAllUsers, null, null,
- Context.RECEIVER_EXPORTED);
-
- final String defaultImiId = mSettings.getSelectedInputMethod();
- final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
- updateFromSettingsLocked(true);
- InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, currentUserId),
- mSettings.getEnabledInputMethodListLocked());
- }
- }
- }
-
- /**
- * Shutdown this service.
- *
- * This service can't be re-used once this method is invoked.
- */
- void systemShutdown() {
- synchronized (ImfLock.class) {
- mContext.unregisterReceiver(mBroadcastReceiver);
- }
- }
-
- /**
- * Returns true iff the caller is identified to be the current input method with the token.
- * @param token The window token given to the input method when it was started.
- * @return true if and only if non-null valid token is specified.
- */
- @GuardedBy("ImfLock.class")
- private boolean calledWithValidTokenLocked(@NonNull IBinder token) {
- if (token == null) {
- throw new InvalidParameterException("token must not be null.");
- }
- if (token != getCurTokenLocked()) {
- Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
- + " uid:" + Binder.getCallingUid() + " token:" + token);
- return false;
- }
- return true;
- }
-
- @BinderThread
- @Nullable
- @Override
- public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
- synchronized (ImfLock.class) {
- return queryDefaultInputMethodForUserIdLocked(userId);
- }
- }
-
- @BinderThread
- @NonNull
- @Override
- public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
- @DirectBootAwareness int directBootAwareness) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
- synchronized (ImfLock.class) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getCurrentUserId(), null);
- if (resolvedUserIds.length != 1) {
- return Collections.emptyList();
- }
- final int callingUid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- return getInputMethodListLocked(
- resolvedUserIds[0], directBootAwareness, callingUid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
- synchronized (ImfLock.class) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getCurrentUserId(), null);
- if (resolvedUserIds.length != 1) {
- return Collections.emptyList();
- }
- final int callingUid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- return getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
-
- synchronized (ImfLock.class) {
- if (!isStylusHandwritingEnabled(mContext, userId)) {
- return false;
- }
-
- // Check if selected IME of current user supports handwriting.
- if (userId == mSettings.getCurrentUserId()) {
- return mBindingController.supportsStylusHandwriting();
- }
-
- //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
- //TODO(b/210039666): use cache.
- final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
- userId, true);
- final InputMethodInfo imi = methodMap.get(settings.getSelectedInputMethod());
- return imi != null && imi.supportsStylusHandwriting();
- }
- }
-
- private boolean isStylusHandwritingEnabled(
- @NonNull Context context, @UserIdInt int userId) {
- // If user is a profile, use preference of it`s parent profile.
- final int profileParentUserId = mUserManagerInternal.getProfileParentId(userId);
- if (Settings.Secure.getIntForUser(context.getContentResolver(),
- STYLUS_HANDWRITING_ENABLED, STYLUS_HANDWRITING_DEFAULT_VALUE,
- profileParentUserId) == 0) {
- return false;
- }
- return true;
- }
-
- @GuardedBy("ImfLock.class")
- private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
- @DirectBootAwareness int directBootAwareness, int callingUid) {
- final ArrayList<InputMethodInfo> methodList;
- final InputMethodSettings settings;
- if (userId == mSettings.getCurrentUserId()
- && directBootAwareness == DirectBootAwareness.AUTO) {
- // Create a copy.
- methodList = new ArrayList<>(mMethodList);
- settings = mSettings;
- } else {
- final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
- methodList = new ArrayList<>();
- final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
- new ArrayMap<>();
- AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
- queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
- methodList, directBootAwareness, mSettings.getEnabledInputMethodNames());
- settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
- }
- // filter caller's access to input methods
- methodList.removeIf(imi ->
- !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
- return methodList;
- }
-
- @GuardedBy("ImfLock.class")
- private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
- int callingUid) {
- final ArrayList<InputMethodInfo> methodList;
- final InputMethodSettings settings;
- if (userId == mSettings.getCurrentUserId()) {
- methodList = mSettings.getEnabledInputMethodListLocked();
- settings = mSettings;
- } else {
- final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
- methodList = settings.getEnabledInputMethodListLocked();
- }
- // filter caller's access to input methods
- methodList.removeIf(imi ->
- !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
- return methodList;
- }
-
- @GuardedBy("ImfLock.class")
- void performOnCreateInlineSuggestionsRequestLocked() {
- mAutofillController.performOnCreateInlineSuggestionsRequest();
- }
-
- /**
- * Sets current host input token.
- *
- * @param callerImeToken the token has been made for the current active input method
- * @param hostInputToken the host input token of the current active input method
- */
- void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(callerImeToken)) {
- return;
- }
- mCurHostInputToken = hostInputToken;
- }
- }
-
- /**
- * Gets enabled subtypes of the specified {@link InputMethodInfo}.
- *
- * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}.
- * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled
- * subtypes.
- * @param userId the user ID to be queried about.
- */
- @Override
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
- boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
-
- synchronized (ImfLock.class) {
- final int callingUid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- return getEnabledInputMethodSubtypeListLocked(imiId,
- allowsImplicitlyEnabledSubtypes, userId, callingUid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
- boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getCurrentUserId()) {
- final InputMethodInfo imi;
- String selectedMethodId = getSelectedMethodIdLocked();
- if (imiId == null && selectedMethodId != null) {
- imi = mMethodMap.get(selectedMethodId);
- } else {
- imi = mMethodMap.get(imiId);
- }
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- return Collections.emptyList();
- }
- return mSettings.getEnabledInputMethodSubtypeListLocked(
- imi, allowsImplicitlyEnabledSubtypes);
- }
- final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodInfo imi = methodMap.get(imiId);
- if (imi == null) {
- return Collections.emptyList();
- }
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
- true);
- if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) {
- return Collections.emptyList();
- }
- return settings.getEnabledInputMethodSubtypeListLocked(
- imi, allowsImplicitlyEnabledSubtypes);
- }
-
- /**
- * Called by each application process as a preparation to start interacting with
- * {@link CarInputMethodManagerService}.
- *
- * <p>As a general principle, IPCs from the application process that take
- * {@link IInputMethodClient} will be rejected without this step.</p>
- *
- * @param client {@link android.os.Binder} proxy that is associated with the singleton instance
- * of {@link android.view.inputmethod.InputMethodManager} that runs on the client
- * process
- * @param inputConnection communication channel for the fallback {@link InputConnection}
- * @param selfReportedDisplayId self-reported display ID to which the client is associated.
- * Whether the client is still allowed to access to this display
- * or not needs to be evaluated every time the client interacts
- * with the display
- */
- @Override
- public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
- int selfReportedDisplayId) {
- // Here there are two scenarios where this method is called:
- // A. IMM is being instantiated in a different process and this is an IPC from that process
- // B. IMM is being instantiated in the same process but Binder.clearCallingIdentity() is
- // called in the caller side if necessary.
- // In either case the following UID/PID should be the ones where InputMethodManager is
- // actually running.
- final int callerUid = Binder.getCallingUid();
- final int callerPid = Binder.getCallingPid();
- synchronized (ImfLock.class) {
- // TODO: Optimize this linear search.
- final int numClients = mClients.size();
- for (int i = 0; i < numClients; ++i) {
- final ClientState state = mClients.valueAt(i);
- if (state.mUid == callerUid && state.mPid == callerPid
- && state.mSelfReportedDisplayId == selfReportedDisplayId) {
- throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
- + "/displayId=" + selfReportedDisplayId + " is already registered.");
- }
- }
- final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
- try {
- client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- // We cannot fully avoid race conditions where the client UID already lost the access to
- // the given self-reported display ID, even if the client is not maliciously reporting
- // a fake display ID. Unconditionally returning SecurityException just because the
- // client doesn't pass display ID verification can cause many test failures hence not an
- // option right now. At the same time
- // context.getSystemService(InputMethodManager.class)
- // is expected to return a valid non-null instance at any time if we do not choose to
- // have the client crash. Thus we do not verify the display ID at all here. Instead we
- // later check the display ID every time the client needs to interact with the specified
- // display.
- final IInputMethodClientInvoker clientInvoker =
- IInputMethodClientInvoker.create(client, mHandler);
- mClients.put(client.asBinder(), new ClientState(clientInvoker, inputConnection,
- callerUid, callerPid, selfReportedDisplayId, deathRecipient));
- }
- }
-
- void removeClient(IInputMethodClient client) {
- synchronized (ImfLock.class) {
- ClientState cs = mClients.remove(client.asBinder());
- if (cs != null) {
- client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
- clearClientSessionLocked(cs);
- clearClientSessionForAccessibilityLocked(cs);
-
- final int numItems = mVirtualDisplayIdToParentMap.size();
- for (int i = numItems - 1; i >= 0; --i) {
- final VirtualDisplayInfo info = mVirtualDisplayIdToParentMap.valueAt(i);
- if (info.mParentClient == cs) {
- mVirtualDisplayIdToParentMap.removeAt(i);
- }
- }
-
- if (mCurClient == cs) {
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
- null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
- if (mBoundToMethod) {
- mBoundToMethod = false;
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null) {
- // When we unbind input, we are unbinding the client, so we always
- // unbind ime and a11y together.
- curMethod.unbindInput();
- AccessibilityManagerInternal.get().unbindInput();
- }
- }
- mBoundToAccessibility = false;
- mCurClient = null;
- mCurVirtualDisplayToScreenMatrix = null;
- }
- if (mCurFocusedWindowClient == cs) {
- mCurFocusedWindowClient = null;
- mCurFocusedWindowEditorInfo = null;
- }
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
- if (mCurClient != null) {
- if (DEBUG) {
- Slog.v(TAG, "unbindCurrentInputLocked: client="
- + mCurClient.mClient.asBinder());
- }
- if (mBoundToMethod) {
- mBoundToMethod = false;
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null) {
- curMethod.unbindInput();
- }
- }
- mBoundToAccessibility = false;
-
- // Since we set active false to current client and set mCurClient to null, let's unbind
- // all accessibility too. That means, when input method get disconnected (including
- // switching ime), we also unbind accessibility
- mCurClient.mClient.setActive(false /* active */, false /* fullscreen */);
- mCurClient.mClient.onUnbindMethod(getSequenceNumberLocked(), unbindClientReason);
- mCurClient.mSessionRequested = false;
- mCurClient.mSessionRequestedForAccessibility = false;
- mCurClient = null;
- mCurVirtualDisplayToScreenMatrix = null;
- ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
- mCurStatsToken = null;
-
- mMenuController.hideInputMethodMenuLocked();
- }
- }
-
- /**
- * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states
- * before unbinding the current method.
- */
- @GuardedBy("ImfLock.class")
- void onUnbindCurrentMethodByReset() {
- final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
- if (winState != null && !winState.isRequestedImeVisible()
- && !mVisibilityStateComputer.isInputShown()) {
- // Normally, the focus window will apply the IME visibility state to
- // WindowManager when the IME has applied it. But it would be too late when
- // switching IMEs in between different users. (Since the focused IME will
- // first unbind the service to switch to bind the next user of the IME
- // service, that wouldn't make the attached IME token validity check in time)
- // As a result, we have to notify WM to apply IME visibility before clearing the
- // binding states in the first place.
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken,
- STATE_HIDE_IME);
- }
- }
-
- /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
- @GuardedBy("ImfLock.class")
- boolean hasAttachedClient() {
- return mCurClient != null;
- }
-
- @VisibleForTesting
- void setAttachedClientForTesting(@NonNull ClientState cs) {
- synchronized (ImfLock.class) {
- mCurClient = cs;
- }
- }
-
- @GuardedBy("ImfLock.class")
- void clearInputShownLocked() {
- mVisibilityStateComputer.setInputShown(false);
- }
-
- @GuardedBy("ImfLock.class")
- private boolean isInputShown() {
- return mVisibilityStateComputer.isInputShown();
- }
-
- @GuardedBy("ImfLock.class")
- private boolean isShowRequestedForCurrentWindow() {
- final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
- return state != null && state.isRequestedImeVisible();
- }
-
- @GuardedBy("ImfLock.class")
- @NonNull
- InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
- if (!mBoundToMethod) {
- getCurMethodLocked().bindInput(mCurClient.mBinding);
- mBoundToMethod = true;
- }
-
- final boolean restarting = !initial;
- final Binder startInputToken = new Binder();
- final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(),
- getCurTokenLocked(),
- mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
- UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId,
- mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode,
- getSequenceNumberLocked());
- mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
- mStartInputHistory.addEntry(info);
-
- // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user
- // implicit visibility (e.g. IME[user=10] -> App[user=0]) thus we do this only for the
- // same-user scenarios.
- // That said ignoring cross-user scenario will never affect IMEs that do not have
- // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
- if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.mUid)) {
- mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(),
- null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
- mCurClient.mUid, true /* direct */);
- }
-
- @InputMethodNavButtonFlags
- final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
- final SessionState session = mCurClient.mCurSession;
- setEnabledSessionLocked(session);
- session.mMethod.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting,
- navButtonFlags, mCurImeDispatcher);
- if (isShowRequestedForCurrentWindow()) {
- if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
- // Re-use current statsToken, if it exists.
- final ImeTracker.Token statsToken = mCurStatsToken;
- mCurStatsToken = null;
- showCurrentInputLocked(mCurFocusedWindow, statsToken,
- mVisibilityStateComputer.getImeShowFlags(),
- null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
- }
-
- String curId = getCurIdLocked();
- final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId);
- final boolean suppressesSpellChecker =
- curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
- final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
- createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
- return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
- session.mSession, accessibilityInputMethodSessions,
- (session.mChannel != null ? session.mChannel.dup() : null),
- curId, getSequenceNumberLocked(), mCurVirtualDisplayToScreenMatrix,
- suppressesSpellChecker);
- }
-
- @GuardedBy("ImfLock.class")
- @Nullable
- private Matrix getVirtualDisplayToScreenMatrixLocked(int clientDisplayId, int imeDisplayId) {
- if (clientDisplayId == imeDisplayId) {
- return null;
- }
- int displayId = clientDisplayId;
- Matrix matrix = null;
- while (true) {
- final VirtualDisplayInfo info = mVirtualDisplayIdToParentMap.get(displayId);
- if (info == null) {
- return null;
- }
- if (matrix == null) {
- matrix = new Matrix(info.mMatrix);
- } else {
- matrix.postConcat(info.mMatrix);
- }
- if (info.mParentClient.mSelfReportedDisplayId == imeDisplayId) {
- return matrix;
- }
- displayId = info.mParentClient.mSelfReportedDisplayId;
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void attachNewAccessibilityLocked(@StartInputReason int startInputReason,
- boolean initial) {
- if (!mBoundToAccessibility) {
- AccessibilityManagerInternal.get().bindInput();
- mBoundToAccessibility = true;
- }
-
- // TODO(b/187453053): grantImplicitAccess to accessibility services access? if so, need to
- // record accessibility services uid.
-
- // We don't start input when session for a11y is created. We start input when
- // input method start input, a11y manager service is always on.
- if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) {
- setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions);
- AccessibilityManagerInternal.get().startInput(mCurRemoteAccessibilityInputConnection,
- mCurEditorInfo, !initial /* restarting */);
- }
- }
-
- private SparseArray<IAccessibilityInputMethodSession> createAccessibilityInputMethodSessions(
- SparseArray<AccessibilitySessionState> accessibilitySessions) {
- final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
- new SparseArray<>();
- if (accessibilitySessions != null) {
- for (int i = 0; i < accessibilitySessions.size(); i++) {
- accessibilityInputMethodSessions.append(accessibilitySessions.keyAt(i),
- accessibilitySessions.valueAt(i).mSession);
- }
- }
- return accessibilityInputMethodSessions;
- }
-
- /**
- * Called by {@link #startInputOrWindowGainedFocusInternalLocked} to bind/unbind/attach the
- * selected InputMethod to the given focused IME client.
- *
- * Note that this should be called after validating if the IME client has IME focus.
- *
- * @see WindowManagerInternal#hasInputMethodClientFocus(IBinder, int, int, int)
- */
- @GuardedBy("ImfLock.class")
- @NonNull
- private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
- IRemoteInputConnection inputConnection,
- @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags,
- @StartInputReason int startInputReason,
- int unverifiedTargetSdkVersion,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
- // If no method is currently selected, do nothing.
- final String selectedMethodId = getSelectedMethodIdLocked();
- if (selectedMethodId == null) {
- return InputBindResult.NO_IME;
- }
-
- if (!mSystemReady) {
- // If the system is not yet ready, we shouldn't be running third
- // party code.
- return new InputBindResult(
- InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
- null, null, null, selectedMethodId, getSequenceNumberLocked(), null, false);
- }
-
- if (!InputMethodUtils.checkIfPackageBelongsToUid(mPackageManagerInternal, cs.mUid,
- editorInfo.packageName)) {
- Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
- + " uid=" + cs.mUid + " package=" + editorInfo.packageName);
- return InputBindResult.INVALID_PACKAGE_NAME;
- }
-
- // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
- // session & other conditions.
- ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
- if (winState == null) {
- return InputBindResult.NOT_IME_TARGET_WINDOW;
- }
- final int csDisplayId = cs.mSelfReportedDisplayId;
- mDisplayIdToShowIme = mVisibilityStateComputer.computeImeDisplayId(winState, csDisplayId);
-
- if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
- null /* resultReceiver */,
- SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
- return InputBindResult.NO_IME;
- }
-
- if (mCurClient != cs) {
- prepareClientSwitchLocked(cs);
- }
-
- // Bump up the sequence for this client and attach it.
- advanceSequenceNumberLocked();
- mCurClient = cs;
- mCurInputConnection = inputConnection;
- mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
- mCurImeDispatcher = imeDispatcher;
- mCurVirtualDisplayToScreenMatrix =
- getVirtualDisplayToScreenMatrixLocked(cs.mSelfReportedDisplayId,
- mDisplayIdToShowIme);
- // Override the locale hints if the app is running on a virtual device.
- if (mVdmInternal == null) {
- mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
- }
- if (mVdmInternal != null && editorInfo.hintLocales == null) {
- LocaleList hintsFromVirtualDevice = mVdmInternal.getPreferredLocaleListForUid(cs.mUid);
- if (hintsFromVirtualDevice != null) {
- editorInfo.hintLocales = hintsFromVirtualDevice;
- }
- }
- mCurEditorInfo = editorInfo;
-
- // If configured, we want to avoid starting up the IME if it is not supposed to be showing
- if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
- unverifiedTargetSdkVersion)) {
- if (DEBUG) {
- Slog.d(TAG, "Avoiding IME startup and unbinding current input method.");
- }
- invalidateAutofillSessionLocked();
- mBindingController.unbindCurrentMethod();
- return InputBindResult.NO_EDITOR;
- }
-
- // Check if the input method is changing.
- // We expect the caller has already verified that the client is allowed to access this
- // display ID.
- if (isSelectedMethodBoundLocked()) {
- if (cs.mCurSession != null) {
- // Fast case: if we are already connected to the input method,
- // then just return it.
- // This doesn't mean a11y sessions are there. When a11y service is
- // enabled while this client is switched out, this client doesn't have the session.
- // A11yManagerService will only request missing sessions (will not request existing
- // sessions again). Note when an a11y service is disabled, it will clear its
- // session from all clients, so we don't need to worry about disabled a11y services.
- cs.mSessionRequestedForAccessibility = false;
- requestClientSessionForAccessibilityLocked(cs);
- // we can always attach to accessibility because AccessibilityManagerService is
- // always on.
- attachNewAccessibilityLocked(startInputReason,
- (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
- return attachNewInputLocked(startInputReason,
- (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
- }
-
- InputBindResult bindResult = tryReuseConnectionLocked(cs);
- if (bindResult != null) {
- return bindResult;
- }
- }
-
- mBindingController.unbindCurrentMethod();
-
- return mBindingController.bindCurrentMethod();
- }
-
- @GuardedBy("ImfLock.class")
- void invalidateAutofillSessionLocked() {
- mAutofillController.invalidateAutofillSession();
- }
-
- @GuardedBy("ImfLock.class")
- private boolean shouldPreventImeStartupLocked(
- @NonNull String selectedMethodId,
- @StartInputFlags int startInputFlags,
- int unverifiedTargetSdkVersion) {
- // Fast-path for the majority of cases
- if (!mPreventImeStartupUnlessTextEditor) {
- return false;
- }
- if (isShowRequestedForCurrentWindow()) {
- return false;
- }
- if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
- return false;
- }
- final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
- if (imi == null) {
- return false;
- }
- if (ArrayUtils.contains(mNonPreemptibleInputMethods, imi.getPackageName())) {
- return false;
- }
- return true;
- }
-
- @GuardedBy("ImfLock.class")
- private boolean isSelectedMethodBoundLocked() {
- String curId = getCurIdLocked();
- return curId != null && curId.equals(getSelectedMethodIdLocked())
- && mDisplayIdToShowIme == mCurTokenDisplayId;
- }
-
- @GuardedBy("ImfLock.class")
- private void prepareClientSwitchLocked(ClientState cs) {
- // If the client is changing, we need to switch over to the new
- // one.
- unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT);
- // If the screen is on, inform the new client it is active
- if (mIsInteractive) {
- cs.mClient.setActive(true /* active */, false /* fullscreen */);
- }
- }
-
- @GuardedBy("ImfLock.class")
- @Nullable
- private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) {
- if (hasConnectionLocked()) {
- if (getCurMethodLocked() != null) {
- // Return to client, and we will get back with it when
- // we have had a session made for it.
- requestClientSessionLocked(cs);
- requestClientSessionForAccessibilityLocked(cs);
- return new InputBindResult(
- InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
- null, null, null, getCurIdLocked(), getSequenceNumberLocked(), null, false);
- } else {
- long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked();
- if (bindingDuration < TIME_TO_RECONNECT) {
- // In this case we have connected to the service, but
- // don't yet have its interface. If it hasn't been too
- // long since we did the connection, we'll return to
- // the client and wait to get the service interface so
- // we can report back. If it has been too long, we want
- // to fall through so we can try a disconnect/reconnect
- // to see if we can get back in touch with the service.
- return new InputBindResult(
- InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
- null, null, null, getCurIdLocked(), getSequenceNumberLocked(), null,
- false);
- } else {
- EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
- getSelectedMethodIdLocked(), bindingDuration, 0);
- }
- }
- }
- return null;
- }
-
- @FunctionalInterface
- interface ImeDisplayValidator {
- @DisplayImePolicy int getDisplayImePolicy(int displayId);
- }
-
- /**
- * Find the display where the IME should be shown.
- *
- * @param displayId the ID of the display where the IME client target is.
- * @param checker instance of {@link ImeDisplayValidator} which is used for
- * checking display config to adjust the final target display.
- * @return The ID of the display where the IME should be shown or
- * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of
- * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
- */
- static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
- if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
- return FALLBACK_DISPLAY_ID;
- }
-
- // Show IME window on fallback display when the display doesn't support system decorations
- // or the display is virtual and isn't owned by system for security concern.
- final int result = checker.getDisplayImePolicy(displayId);
- if (result == DISPLAY_IME_POLICY_LOCAL) {
- return displayId;
- } else if (result == DISPLAY_IME_POLICY_HIDE) {
- return INVALID_DISPLAY;
- } else {
- return FALLBACK_DISPLAY_ID;
- }
- }
-
- @GuardedBy("ImfLock.class")
- void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
- if (DEBUG) {
- Slog.v(TAG, "Sending attach of token: " + token + " for display: "
- + mCurTokenDisplayId);
- }
- inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
- getInputMethodNavButtonFlagsLocked());
- }
-
- @AnyThread
- void scheduleResetStylusHandwriting() {
- mHandler.obtainMessage(MSG_RESET_HANDWRITING).sendToTarget();
- }
-
- @AnyThread
- void schedulePrepareStylusHandwritingDelegation(
- @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
- mHandler.obtainMessage(
- MSG_PREPARE_HANDWRITING_DELEGATION,
- new Pair<>(delegatePackageName, delegatorPackageName)).sendToTarget();
- }
-
- @AnyThread
- void scheduleRemoveStylusHandwritingWindow() {
- mHandler.obtainMessage(MSG_REMOVE_HANDWRITING_WINDOW).sendToTarget();
- }
-
- @AnyThread
- void scheduleNotifyImeUidToAudioService(int uid) {
- mHandler.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
- mHandler.obtainMessage(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid, 0 /* unused */)
- .sendToTarget();
- }
-
- @BinderThread
- void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session,
- InputChannel channel) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated");
- try {
- synchronized (ImfLock.class) {
- if (mUserSwitchHandlerTask != null) {
- // We have a pending user-switching task so it's better to just ignore this
- // session.
- channel.dispose();
- return;
- }
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null && method != null
- && curMethod.asBinder() == method.asBinder()) {
- if (mCurClient != null) {
- clearClientSessionLocked(mCurClient);
- mCurClient.mCurSession = new SessionState(mCurClient,
- method, session, channel);
- InputBindResult res = attachNewInputLocked(
- StartInputReason.SESSION_CREATED_BY_IME, true);
- attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true);
- if (res.method != null) {
- mCurClient.mClient.onBindMethod(res);
- }
- return;
- }
- }
- }
-
- // Session abandoned. Close its associated input channel.
- channel.dispose();
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- @GuardedBy("ImfLock.class")
- void resetSystemUiLocked() {
- // Set IME window status as invisible when unbinding current method.
- mImeWindowVis = 0;
- mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
- mCurTokenDisplayId = INVALID_DISPLAY;
- mCurHostInputToken = null;
- }
-
- @GuardedBy("ImfLock.class")
- void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
- setSelectedMethodIdLocked(null);
- // Callback before clean-up binding states.
- onUnbindCurrentMethodByReset();
- mBindingController.unbindCurrentMethod();
- unbindCurrentClientLocked(unbindClientReason);
- }
-
- @GuardedBy("ImfLock.class")
- void reRequestCurrentClientSessionLocked() {
- if (mCurClient != null) {
- clearClientSessionLocked(mCurClient);
- clearClientSessionForAccessibilityLocked(mCurClient);
- requestClientSessionLocked(mCurClient);
- requestClientSessionForAccessibilityLocked(mCurClient);
- }
- }
-
- @GuardedBy("ImfLock.class")
- void requestClientSessionLocked(ClientState cs) {
- if (!cs.mSessionRequested) {
- if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
- final InputChannel serverChannel;
- final InputChannel clientChannel;
- {
- final InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
- serverChannel = channels[0];
- clientChannel = channels[1];
- }
-
- cs.mSessionRequested = true;
-
- final IInputMethodInvoker curMethod = getCurMethodLocked();
- final IInputMethodSessionCallback.Stub callback =
- new IInputMethodSessionCallback.Stub() {
- @Override
- public void sessionCreated(IInputMethodSession session) {
- final long ident = Binder.clearCallingIdentity();
- try {
- onSessionCreated(curMethod, session, serverChannel);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- };
-
- try {
- curMethod.createSession(clientChannel, callback);
- } finally {
- // Dispose the channel because the remote proxy will get its own copy when
- // unparceled.
- if (clientChannel != null) {
- clientChannel.dispose();
- }
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- void requestClientSessionForAccessibilityLocked(ClientState cs) {
- if (!cs.mSessionRequestedForAccessibility) {
- if (DEBUG) Slog.v(TAG, "Creating new accessibility sessions for client " + cs);
- cs.mSessionRequestedForAccessibility = true;
- ArraySet<Integer> ignoreSet = new ArraySet<>();
- for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) {
- ignoreSet.add(cs.mAccessibilitySessions.keyAt(i));
- }
- AccessibilityManagerInternal.get().createImeSession(ignoreSet);
- }
- }
-
- @GuardedBy("ImfLock.class")
- void clearClientSessionLocked(ClientState cs) {
- finishSessionLocked(cs.mCurSession);
- cs.mCurSession = null;
- cs.mSessionRequested = false;
- }
-
- @GuardedBy("ImfLock.class")
- void clearClientSessionForAccessibilityLocked(ClientState cs) {
- for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) {
- finishSessionForAccessibilityLocked(cs.mAccessibilitySessions.valueAt(i));
- }
- cs.mAccessibilitySessions.clear();
- cs.mSessionRequestedForAccessibility = false;
- }
-
- @GuardedBy("ImfLock.class")
- void clearClientSessionForAccessibilityLocked(ClientState cs, int id) {
- AccessibilitySessionState session = cs.mAccessibilitySessions.get(id);
- if (session != null) {
- finishSessionForAccessibilityLocked(session);
- cs.mAccessibilitySessions.remove(id);
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void finishSessionLocked(SessionState sessionState) {
- if (sessionState != null) {
- if (sessionState.mSession != null) {
- try {
- sessionState.mSession.finishSession();
- } catch (RemoteException e) {
- Slog.w(TAG, "Session failed to close due to remote exception", e);
- updateSystemUiLocked(0 /* vis */, mBackDisposition);
- }
- sessionState.mSession = null;
- }
- if (sessionState.mChannel != null) {
- sessionState.mChannel.dispose();
- sessionState.mChannel = null;
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void finishSessionForAccessibilityLocked(AccessibilitySessionState sessionState) {
- if (sessionState != null) {
- if (sessionState.mSession != null) {
- try {
- sessionState.mSession.finishSession();
- } catch (RemoteException e) {
- Slog.w(TAG, "Session failed to close due to remote exception", e);
- }
- sessionState.mSession = null;
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- void clearClientSessionsLocked() {
- if (getCurMethodLocked() != null) {
- final int numClients = mClients.size();
- for (int i = 0; i < numClients; ++i) {
- clearClientSessionLocked(mClients.valueAt(i));
- clearClientSessionForAccessibilityLocked(mClients.valueAt(i));
- }
-
- finishSessionLocked(mEnabledSession);
- for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
- finishSessionForAccessibilityLocked(mEnabledAccessibilitySessions.valueAt(i));
- }
- mEnabledSession = null;
- mEnabledAccessibilitySessions.clear();
- scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
- }
- hideStatusBarIconLocked();
- mInFullscreenMode = false;
- mWindowManagerInternal.setDismissImeOnBackKeyPressed(false);
- }
-
- @BinderThread
- private void updateStatusIcon(@NonNull IBinder token, String packageName,
- @DrawableRes int iconId) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- if (iconId == 0) {
- if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
- hideStatusBarIconLocked();
- } else if (packageName != null) {
- if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
- final PackageManager userAwarePackageManager =
- getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
- ApplicationInfo applicationInfo = null;
- try {
- applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
- PackageManager.ApplicationInfoFlags.of(0));
- } catch (PackageManager.NameNotFoundException e) {
- }
- final CharSequence contentDescription = applicationInfo != null
- ? userAwarePackageManager.getApplicationLabel(applicationInfo)
- : null;
- if (mStatusBarManagerInternal != null) {
- mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
- contentDescription != null
- ? contentDescription.toString() : null);
- mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void hideStatusBarIconLocked() {
- if (mStatusBarManagerInternal != null) {
- mStatusBarManagerInternal.setIconVisibility(mSlotIme, false);
- }
- }
-
- @GuardedBy("ImfLock.class")
- @InputMethodNavButtonFlags
- private int getInputMethodNavButtonFlagsLocked() {
- if (mImeDrawsImeNavBarResLazyInitFuture != null) {
- // TODO(b/225366708): Avoid Future.get(), which is internally used here.
- ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
- "Waiting for the lazy init of mImeDrawsImeNavBarRes");
- }
- final boolean canImeDrawsImeNavBar =
- mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get();
- final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
- InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE);
- return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
- | (shouldShowImeSwitcherWhenImeIsShown
- ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0);
- }
-
- @GuardedBy("ImfLock.class")
- private boolean shouldShowImeSwitcherLocked(int visibility) {
- if (!mShowOngoingImeSwitcherForPhones) return false;
- // When the IME switcher dialog is shown, the IME switcher button should be hidden.
- if (mMenuController.getSwitchingDialogLocked() != null) return false;
- // When we are switching IMEs, the IME switcher button should be hidden.
- if (!Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
- return false;
- }
- if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) {
- return false;
- }
- if ((visibility & InputMethodService.IME_ACTIVE) == 0
- || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
- return false;
- }
- if (mWindowManagerInternal.isHardKeyboardAvailable()) {
- // When physical keyboard is attached, we show the ime switcher (or notification if
- // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
- // exists in the IME switcher dialog. Might be OK to remove this condition once
- // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
- return true;
- } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
- return false;
- }
-
- List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilterLocked(
- InputMethodInfo::shouldShowInInputMethodPicker);
- final int numImes = imes.size();
- if (numImes > 2) return true;
- if (numImes < 1) return false;
- int nonAuxCount = 0;
- int auxCount = 0;
- InputMethodSubtype nonAuxSubtype = null;
- InputMethodSubtype auxSubtype = null;
- for (int i = 0; i < numImes; ++i) {
- final InputMethodInfo imi = imes.get(i);
- final List<InputMethodSubtype> subtypes =
- mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
- final int subtypeCount = subtypes.size();
- if (subtypeCount == 0) {
- ++nonAuxCount;
- } else {
- for (int j = 0; j < subtypeCount; ++j) {
- final InputMethodSubtype subtype = subtypes.get(j);
- if (!subtype.isAuxiliary()) {
- ++nonAuxCount;
- nonAuxSubtype = subtype;
- } else {
- ++auxCount;
- auxSubtype = subtype;
- }
- }
- }
- }
- if (nonAuxCount > 1 || auxCount > 1) {
- return true;
- } else if (nonAuxCount == 1 && auxCount == 1) {
- if (nonAuxSubtype != null && auxSubtype != null
- && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
- || auxSubtype.overridesImplicitlyEnabledSubtype()
- || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
- && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
- return false;
- }
- return true;
- }
- return false;
- }
-
- @BinderThread
- @SuppressWarnings("deprecation")
- private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) {
- final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
-
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return;
- }
- // Skip update IME status when current token display is not same as focused display.
- // Note that we still need to update IME status when focusing external display
- // that does not support system decoration and fallback to show IME on default
- // display since it is intentional behavior.
- if (mCurTokenDisplayId != topFocusedDisplayId
- && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) {
- return;
- }
- mImeWindowVis = vis;
- mBackDisposition = backDisposition;
- updateSystemUiLocked(vis, backDisposition);
- }
-
- final boolean dismissImeOnBackKeyPressed;
- switch (backDisposition) {
- case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
- dismissImeOnBackKeyPressed = true;
- break;
- case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
- dismissImeOnBackKeyPressed = false;
- break;
- default:
- case InputMethodService.BACK_DISPOSITION_DEFAULT:
- dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0);
- break;
- }
- mWindowManagerInternal.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed);
- }
-
- @BinderThread
- private void reportStartInput(@NonNull IBinder token, IBinder startInputToken) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return;
- }
- final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
- if (targetWindow != null) {
- mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow);
- }
- mLastImeTargetWindow = targetWindow;
- }
- }
-
- private void updateImeWindowStatus(boolean disableImeIcon) {
- synchronized (ImfLock.class) {
- if (disableImeIcon) {
- updateSystemUiLocked(0, mBackDisposition);
- } else {
- updateSystemUiLocked();
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- void updateSystemUiLocked() {
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
- }
-
- // Caution! This method is called in this class. Handle multi-user carefully
- @GuardedBy("ImfLock.class")
- private void updateSystemUiLocked(int vis, int backDisposition) {
- if (getCurTokenLocked() == null) {
- return;
- }
- if (DEBUG) {
- Slog.d(TAG, "IME window vis: " + vis
- + " active: " + (vis & InputMethodService.IME_ACTIVE)
- + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
- + " displayId: " + mCurTokenDisplayId);
- }
-
- // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
- // all updateSystemUi happens on system privilege.
- final long ident = Binder.clearCallingIdentity();
- try {
- if (!mCurPerceptible) {
- if ((vis & InputMethodService.IME_VISIBLE) != 0) {
- vis &= ~InputMethodService.IME_VISIBLE;
- vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
- }
- } else {
- vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
- }
- if (mMenuController.getSwitchingDialogLocked() != null
- || !Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
- // When the IME switcher dialog is shown, or we are switching IMEs,
- // the back button should be in the default state (as if the IME is not shown).
- backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING;
- }
- final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
- if (mStatusBarManagerInternal != null) {
- mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId,
- getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @GuardedBy("ImfLock.class")
- void updateFromSettingsLocked(boolean enabledMayChange) {
- updateInputMethodsFromSettingsLocked(enabledMayChange);
- mMenuController.updateKeyboardFromSettingsLocked();
- }
-
- @GuardedBy("ImfLock.class")
- void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
- if (enabledMayChange) {
- final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
- mSettings.getCurrentUserId());
-
- List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
- for (int i = 0; i < enabled.size(); i++) {
- // We allow the user to select "disabled until used" apps, so if they
- // are enabling one of those here we now need to make it enabled.
- InputMethodInfo imm = enabled.get(i);
- ApplicationInfo ai = null;
- try {
- ai = userAwarePackageManager.getApplicationInfo(imm.getPackageName(),
- PackageManager.ApplicationInfoFlags.of(
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS));
- } catch (PackageManager.NameNotFoundException ignored) {
- }
- if (ai != null && ai.enabledSetting
- == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
- if (DEBUG) {
- Slog.d(TAG, "Update state(" + imm.getId()
- + "): DISABLED_UNTIL_USED -> DEFAULT");
- }
- userAwarePackageManager.setApplicationEnabledSetting(imm.getPackageName(),
- PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
- PackageManager.DONT_KILL_APP);
- }
- }
- }
- // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
- // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
- // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
- // enabled.
- String id = mSettings.getSelectedInputMethod();
- // There is no input method selected, try to choose new applicable input method.
- if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
- id = mSettings.getSelectedInputMethod();
- }
- if (!TextUtils.isEmpty(id)) {
- try {
- setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
- } catch (IllegalArgumentException e) {
- Slog.w(TAG, "Unknown input method from prefs: " + id, e);
- resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED);
- }
- } else {
- // There is no longer an input method set, so stop any current one.
- resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
- }
- // Here is not the perfect place to reset the switching controller. Ideally
- // mSwitchingController and mSettings should be able to share the same state.
- // TODO: Make sure that mSwitchingController and mSettings are sharing the
- // the same enabled IMEs list.
- mSwitchingController.resetCircularListLocked(mContext);
- mHardwareKeyboardShortcutController.reset(mSettings);
-
- sendOnNavButtonFlagsChangedLocked();
- }
-
- @GuardedBy("ImfLock.class")
- private void notifyInputMethodSubtypeChangedLocked(@UserIdInt int userId,
- @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
- final InputMethodSubtype normalizedSubtype =
- subtype != null && subtype.isSuitableForPhysicalKeyboardLayoutMapping()
- ? subtype : null;
- final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
- ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
- mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
- userId, newSubtypeHandle, normalizedSubtype);
- }
-
- @GuardedBy("ImfLock.class")
- void setInputMethodLocked(String id, int subtypeId) {
- InputMethodInfo info = mMethodMap.get(id);
- if (info == null) {
- throw getExceptionForUnknownImeId(id);
- }
-
- // See if we need to notify a subtype change within the same IME.
- if (id.equals(getSelectedMethodIdLocked())) {
- final int userId = mSettings.getCurrentUserId();
- final int subtypeCount = info.getSubtypeCount();
- if (subtypeCount <= 0) {
- notifyInputMethodSubtypeChangedLocked(userId, info, null);
- return;
- }
- final InputMethodSubtype oldSubtype = mCurrentSubtype;
- final InputMethodSubtype newSubtype;
- if (subtypeId >= 0 && subtypeId < subtypeCount) {
- newSubtype = info.getSubtypeAt(subtypeId);
- } else {
- // If subtype is null, try to find the most applicable one from
- // getCurrentInputMethodSubtype.
- newSubtype = getCurrentInputMethodSubtypeLocked();
- }
- if (newSubtype == null || oldSubtype == null) {
- Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
- + ", new subtype = " + newSubtype);
- notifyInputMethodSubtypeChangedLocked(userId, info, null);
- return;
- }
- if (!newSubtype.equals(oldSubtype)) {
- setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null) {
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
- curMethod.changeInputMethodSubtype(newSubtype);
- }
- }
- return;
- }
-
- // Changing to a different IME.
- final long ident = Binder.clearCallingIdentity();
- try {
- // Set a subtype to this input method.
- // subtypeId the name of a subtype which will be set.
- setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
- // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
- // because mCurMethodId is stored as a history in
- // setSelectedInputMethodAndSubtypeLocked().
- setSelectedMethodIdLocked(id);
-
- if (mActivityManagerInternal.isSystemReady()) {
- Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("input_method_id", id);
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- }
- unbindCurrentClientLocked(UnbindReason.SWITCH_IME);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
- @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
- int uid = Binder.getCallingUid();
- ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#showSoftInput");
- synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- return false;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
- return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType,
- resultReceiver, reason);
- } finally {
- Binder.restoreCallingIdentity(ident);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
- }
-
- @BinderThread
- @Override
- public void startStylusHandwriting(IInputMethodClient client) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
- try {
- ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#startStylusHandwriting");
- int uid = Binder.getCallingUid();
- synchronized (ImfLock.class) {
- mHwController.clearPendingHandwritingDelegation();
- if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
- null /* statsToken */)) {
- return;
- }
- if (!hasSupportedStylusLocked()) {
- Slog.w(TAG, "No supported Stylus hardware found on device. Ignoring"
- + " startStylusHandwriting()");
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- if (!mBindingController.supportsStylusHandwriting()) {
- Slog.w(TAG,
- "Stylus HW unsupported by IME. Ignoring startStylusHandwriting()");
- return;
- }
-
- final OptionalInt requestId = mHwController.getCurrentRequestId();
- if (!requestId.isPresent()) {
- Slog.e(TAG, "Stylus handwriting was not initialized.");
- return;
- }
- if (!mHwController.isStylusGestureOngoing()) {
- Slog.e(TAG,
- "There is no ongoing stylus gesture to start stylus handwriting.");
- return;
- }
- if (mHwController.hasOngoingStylusHandwritingSession()) {
- // prevent duplicate calls to startStylusHandwriting().
- Slog.e(TAG,
- "Stylus handwriting session is already ongoing."
- + " Ignoring startStylusHandwriting().");
- return;
- }
- if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
- final IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null) {
- curMethod.canStartStylusHandwriting(requestId.getAsInt());
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- @Override
- public void prepareStylusHandwritingDelegation(
- @NonNull IInputMethodClient client,
- @UserIdInt int userId,
- @NonNull String delegatePackageName,
- @NonNull String delegatorPackageName) {
- if (!isStylusHandwritingEnabled(mContext, userId)) {
- Slog.w(TAG, "Can not prepare stylus handwriting delegation. Stylus handwriting"
- + " pref is disabled for user: " + userId);
- return;
- }
- if (!verifyClientAndPackageMatch(client, delegatorPackageName)) {
- Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
- throw new IllegalArgumentException("Delegator doesn't match Uid");
- }
- schedulePrepareStylusHandwritingDelegation(delegatePackageName, delegatorPackageName);
- }
-
- @Override
- public boolean acceptStylusHandwritingDelegation(
- @NonNull IInputMethodClient client,
- @UserIdInt int userId,
- @NonNull String delegatePackageName,
- @NonNull String delegatorPackageName) {
- if (!isStylusHandwritingEnabled(mContext, userId)) {
- Slog.w(TAG, "Can not accept stylus handwriting delegation. Stylus handwriting"
- + " pref is disabled for user: " + userId);
- return false;
- }
- if (!verifyDelegator(client, delegatePackageName, delegatorPackageName)) {
- return false;
- }
-
- startStylusHandwriting(client);
- return true;
- }
-
- private boolean verifyClientAndPackageMatch(
- @NonNull IInputMethodClient client, @NonNull String packageName) {
- ClientState cs;
- synchronized (ImfLock.class) {
- cs = mClients.get(client.asBinder());
- }
- if (cs == null) {
- throw new IllegalArgumentException("unknown client " + client.asBinder());
- }
- return InputMethodUtils.checkIfPackageBelongsToUid(
- mPackageManagerInternal, cs.mUid, packageName);
- }
-
- private boolean verifyDelegator(
- @NonNull IInputMethodClient client,
- @NonNull String delegatePackageName,
- @NonNull String delegatorPackageName) {
- if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
- Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
- + " startStylusHandwriting");
- return false;
- }
- synchronized (ImfLock.class) {
- if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName())) {
- Slog.w(TAG,
- "Delegator package does not match. Ignoring startStylusHandwriting");
- return false;
- }
- if (!delegatePackageName.equals(mHwController.getDelegatePackageName())) {
- Slog.w(TAG,
- "Delegate package does not match. Ignoring startStylusHandwriting");
- return false;
- }
- }
- return true;
- }
-
- @BinderThread
- @Override
- public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
- Binder.withCleanCallingIdentity(() -> {
- Objects.requireNonNull(windowToken, "windowToken must not be null");
- synchronized (ImfLock.class) {
- if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
- return;
- }
- mCurPerceptible = perceptible;
- updateSystemUiLocked();
- }
- });
- }
-
- @GuardedBy("ImfLock.class")
- boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
- int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- return showCurrentInputLocked(windowToken, statsToken, flags,
- MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
- }
-
- @GuardedBy("ImfLock.class")
- private boolean showCurrentInputLocked(IBinder windowToken,
- @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- // Create statsToken is none exists.
- if (statsToken == null) {
- statsToken = createStatsTokenForFocusedClient(true /* show */,
- ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
- }
-
- if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) {
- return false;
- }
-
- if (!mSystemReady) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
- return false;
- }
- 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.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
- if (curMethod != null) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
- mCurStatsToken = null;
-
- if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
- curMethod.updateEditorToolType(lastClickToolType);
- }
- mVisibilityApplier.performShowIme(windowToken, statsToken,
- mVisibilityStateComputer.getImeShowFlags(), resultReceiver, reason);
- mVisibilityStateComputer.setInputShown(true);
- return true;
- } else {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
- mCurStatsToken = statsToken;
- }
- return false;
- }
-
- @Override
- public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
- @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
- int uid = Binder.getCallingUid();
- ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#hideSoftInput");
- synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
- if (isInputShown()) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
- } else {
- ImeTracker.forLogging().onCancelled(statsToken,
- ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
- }
- return false;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
- if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
- return CarInputMethodManagerService.this.hideCurrentInputLocked(windowToken,
- statsToken, flags, resultReceiver, reason);
- } finally {
- Binder.restoreCallingIdentity(ident);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
- int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- // Create statsToken is none exists.
- if (statsToken == null) {
- statsToken = createStatsTokenForFocusedClient(false /* show */,
- ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
- }
-
- if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
- return false;
- }
-
- // There is a chance that IMM#hideSoftInput() is called in a transient state where
- // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
- // to be updated with the new value sent from IME process. Even in such a transient state
- // historically we have accepted an incoming call of IMM#hideSoftInput() from the
- // application process as a valid request, and have even promised such a behavior with CTS
- // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
- // IMMS#InputShown indicates that the software keyboard is shown.
- // TODO(b/246309664): Clean up IMMS#mImeWindowVis
- IInputMethodInvoker curMethod = getCurMethodLocked();
- final boolean shouldHideSoftInput = curMethod != null
- && (isInputShown() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
-
- mVisibilityStateComputer.requestImeVisibility(windowToken, false);
- if (shouldHideSoftInput) {
- // The IME will report its visible state again after the following message finally
- // 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.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
- mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason);
- } else {
- 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.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
- mCurStatsToken = null;
- return shouldHideSoftInput;
- }
-
- private boolean isImeClientFocused(IBinder windowToken, ClientState cs) {
- final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
- windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
- return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS;
- }
-
- @NonNull
- @Override
- public InputBindResult startInputOrWindowGainedFocus(
- @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
- @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
- int windowFlags, @Nullable EditorInfo editorInfo,
- IRemoteInputConnection inputConnection,
- IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
-
- if (editorInfo == null || editorInfo.targetInputMethodUser == null
- || editorInfo.targetInputMethodUser.getIdentifier() != userId) {
- throw new InvalidParameterException("EditorInfo#targetInputMethodUser must also be "
- + "specified for cross-user startInputOrWindowGainedFocus()");
- }
- }
-
- if (windowToken == null) {
- Slog.e(TAG, "windowToken cannot be null.");
- return InputBindResult.NULL;
- }
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "IMMS.startInputOrWindowGainedFocus");
- ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#startInputOrWindowGainedFocus");
- final InputBindResult result;
- synchronized (ImfLock.class) {
- final long ident = Binder.clearCallingIdentity();
- try {
- result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
- client, windowToken, startInputFlags, softInputMode, windowFlags,
- editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- if (result == null) {
- // This must never happen, but just in case.
- Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
- + InputMethodDebug.startInputReasonToString(startInputReason)
- + " windowFlags=#" + Integer.toHexString(windowFlags)
- + " editorInfo=" + editorInfo);
- return InputBindResult.NULL;
- }
-
- return result;
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- @GuardedBy("ImfLock.class")
- @NonNull
- private InputBindResult startInputOrWindowGainedFocusInternalLocked(
- @StartInputReason int startInputReason, IInputMethodClient client,
- @NonNull IBinder windowToken, @StartInputFlags int startInputFlags,
- @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo,
- IRemoteInputConnection inputContext,
- @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
- if (DEBUG) {
- Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
- + InputMethodDebug.startInputReasonToString(startInputReason)
- + " client=" + client.asBinder()
- + " inputContext=" + inputContext
- + " editorInfo=" + editorInfo
- + " startInputFlags="
- + InputMethodDebug.startInputFlagsToString(startInputFlags)
- + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
- + " windowFlags=#" + Integer.toHexString(windowFlags)
- + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
- + " userId=" + userId
- + " imeDispatcher=" + imeDispatcher);
- }
-
- if (!mUserManagerInternal.isUserRunning(userId)) {
- // There is a chance that we hit here because of race condition. Let's just
- // return an error code instead of crashing the caller process, which at
- // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
- // important process.
- Slog.w(TAG, "User #" + userId + " is not running.");
- return InputBindResult.INVALID_USER;
- }
-
- final ClientState cs = mClients.get(client.asBinder());
- if (cs == null) {
- throw new IllegalArgumentException("unknown client " + client.asBinder());
- }
-
- final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
- windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
- switch (imeClientFocus) {
- case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
- Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch.");
- return InputBindResult.DISPLAY_ID_MISMATCH;
- case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
- // Check with the window manager to make sure this client actually
- // has a window with focus. If not, reject. This is thread safe
- // because if the focus changes some time before or after, the
- // next client receiving focus that has any interest in input will
- // be calling through here after that change happens.
- if (DEBUG) {
- Slog.w(TAG, "Focus gain on non-focused client " + cs.mClient
- + " (uid=" + cs.mUid + " pid=" + cs.mPid + ")");
- }
- return InputBindResult.NOT_IME_TARGET_WINDOW;
- case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
- return InputBindResult.INVALID_DISPLAY_ID;
- }
-
- if (mUserSwitchHandlerTask != null) {
- // There is already an on-going pending user switch task.
- final int nextUserId = mUserSwitchHandlerTask.mToUserId;
- if (userId == nextUserId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getCurrentUserId(), false /* enabledOnly */);
- for (int profileId : profileIdsWithDisabled) {
- if (profileId == userId) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- }
- return InputBindResult.INVALID_USER;
- }
-
- final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
- // In case mShowForced flag affects the next client to keep IME visible, when the current
- // client is leaving due to the next focused client, we clear mShowForced flag when the
- // next client's targetSdkVersion is T or higher.
- final boolean showForced = mVisibilityStateComputer.mShowForced;
- if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
- mVisibilityStateComputer.mShowForced = false;
- }
-
- // cross-profile access is always allowed here to allow profile-switching.
- if (!mSettings.isCurrentProfile(userId)) {
- Slog.w(TAG, "A background user is requesting window. Hiding IME.");
- Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
- + " a background user, use EditorInfo.targetInputMethodUser with"
- + " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
- null /* resultReceiver */, SoftInputShowHideReason.HIDE_INVALID_USER);
- return InputBindResult.INVALID_USER;
- }
-
- if (userId != mSettings.getCurrentUserId()) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
-
- final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
- final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
- final boolean startInputByWinGainedFocus =
- (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
-
- // Init the focused window state (e.g. whether the editor has focused or IME focus has
- // changed from another window).
- final ImeTargetWindowState windowState = new ImeTargetWindowState(softInputMode,
- windowFlags, !sameWindowFocused, isTextEditor, startInputByWinGainedFocus);
- mVisibilityStateComputer.setWindowState(windowToken, windowState);
-
- if (sameWindowFocused && isTextEditor) {
- if (DEBUG) {
- Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
- + " editorInfo=" + editorInfo + ", token = " + windowToken
- + ", startInputReason="
- + InputMethodDebug.startInputReasonToString(startInputReason));
- }
- if (editorInfo != null) {
- return startInputUncheckedLocked(cs, inputContext,
- remoteAccessibilityInputConnection, editorInfo, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion, imeDispatcher);
- }
- return new InputBindResult(
- InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
- null, null, null, null, -1, null, false);
- }
-
- mCurFocusedWindow = windowToken;
- mCurFocusedWindowSoftInputMode = softInputMode;
- mCurFocusedWindowClient = cs;
- mCurFocusedWindowEditorInfo = editorInfo;
- mCurPerceptible = true;
-
- // We want to start input before showing the IME, but after closing
- // it. We want to do this after closing it to help the IME disappear
- // more quickly (not get stuck behind it initializing itself for the
- // new focused input, even if its window wants to hide the IME).
- boolean didStart = false;
- InputBindResult res = null;
-
- final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.computeState(windowState,
- isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags));
- if (imeVisRes != null) {
- switch (imeVisRes.getReason()) {
- case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY:
- case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV:
- case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV:
- case SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE:
- if (editorInfo != null) {
- res = startInputUncheckedLocked(cs, inputContext,
- remoteAccessibilityInputConnection, editorInfo, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion,
- imeDispatcher);
- didStart = true;
- }
- break;
- }
-
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */,
- imeVisRes.getState(), imeVisRes.getReason());
-
- if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) {
- // If focused display changed, we should unbind current method
- // to make app window in previous display relayout after Ime
- // window token removed.
- // Note that we can trust client's display ID as long as it matches
- // to the display ID obtained from the window.
- if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
- mBindingController.unbindCurrentMethod();
- }
- }
- }
- if (!didStart) {
- if (editorInfo != null) {
- res = startInputUncheckedLocked(cs, inputContext,
- remoteAccessibilityInputConnection, editorInfo, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion,
- imeDispatcher);
- } else {
- res = InputBindResult.NULL_EDITOR_INFO;
- }
- }
- return res;
- }
-
- @GuardedBy("ImfLock.class")
- private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken,
- @SoftInputShowHideReason int reason) {
- showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT,
- null /* resultReceiver */, reason);
- }
-
- @GuardedBy("ImfLock.class")
- private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName,
- @Nullable ImeTracker.Token statsToken) {
- if (mCurClient == null || client == null
- || mCurClient.mClient.asBinder() != client.asBinder()) {
- // We need to check if this is the current client with
- // focus in the window manager, to allow this call to
- // be made before input is started in it.
- final ClientState cs = mClients.get(client.asBinder());
- if (cs == null) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
- throw new IllegalArgumentException("unknown client " + client.asBinder());
- }
- 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.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
- return true;
- }
-
- @GuardedBy("ImfLock.class")
- private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
- final int uid = Binder.getCallingUid();
- if (mCurFocusedWindowClient != null && client != null
- && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
- return true;
- }
- if (mSettings.getCurrentUserId() != UserHandle.getUserId(uid)) {
- return false;
- }
- if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
- mPackageManagerInternal,
- uid,
- getCurIntentLocked().getComponent().getPackageName())) {
- return true;
- }
- return false;
- }
-
- @Override
- public void showInputMethodPickerFromClient(IInputMethodClient client,
- int auxiliarySubtypeMode) {
- synchronized (ImfLock.class) {
- if (!canShowInputMethodPickerLocked(client)) {
- Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
- + Binder.getCallingUid() + ": " + client);
- return;
- }
-
- // Always call subtype picker, because subtype picker is a superset of input method
- // picker.
- final int displayId =
- (mCurClient != null) ? mCurClient.mSelfReportedDisplayId : DEFAULT_DISPLAY;
- mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
- .sendToTarget();
- }
- }
-
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
- @Override
- public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
- // Always call subtype picker, because subtype picker is a superset of input method
- // picker.
- super.showInputMethodPickerFromSystem_enforcePermission();
-
- mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
- .sendToTarget();
- }
-
- /**
- * A test API for CTS to make sure that the input method menu is showing.
- */
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
- public boolean isInputMethodPickerShownForTest() {
- super.isInputMethodPickerShownForTest_enforcePermission();
-
- synchronized (ImfLock.class) {
- return mMenuController.isisInputMethodPickerShownForTestLocked();
- }
- }
-
- @NonNull
- private static IllegalArgumentException getExceptionForUnknownImeId(
- @Nullable String imeId) {
- return new IllegalArgumentException("Unknown id: " + imeId);
- }
-
- @BinderThread
- private void setInputMethod(@NonNull IBinder token, String id) {
- final int callingUid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(callingUid);
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return;
- }
- final InputMethodInfo imi = mMethodMap.get(id);
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- throw getExceptionForUnknownImeId(id);
- }
- setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
- }
- }
-
- @BinderThread
- private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
- InputMethodSubtype subtype) {
- final int callingUid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(callingUid);
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return;
- }
- final InputMethodInfo imi = mMethodMap.get(id);
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- throw getExceptionForUnknownImeId(id);
- }
- if (subtype != null) {
- setInputMethodWithSubtypeIdLocked(token, id,
- SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()));
- } else {
- setInputMethod(token, id);
- }
- }
- }
-
- @BinderThread
- private boolean switchToPreviousInputMethod(@NonNull IBinder token) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return false;
- }
- final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
- final InputMethodInfo lastImi;
- if (lastIme != null) {
- lastImi = mMethodMap.get(lastIme.first);
- } else {
- lastImi = null;
- }
- String targetLastImiId = null;
- int subtypeId = NOT_A_SUBTYPE_ID;
- if (lastIme != null && lastImi != null) {
- final boolean imiIdIsSame = lastImi.getId().equals(getSelectedMethodIdLocked());
- final int lastSubtypeHash = Integer.parseInt(lastIme.second);
- final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
- : mCurrentSubtype.hashCode();
- // If the last IME is the same as the current IME and the last subtype is not
- // defined, there is no need to switch to the last IME.
- if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
- targetLastImiId = lastIme.first;
- subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
- }
- }
-
- if (TextUtils.isEmpty(targetLastImiId)
- && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
- // This is a safety net. If the currentSubtype can't be added to the history
- // and the framework couldn't find the last ime, we will make the last ime be
- // the most applicable enabled keyboard subtype of the system imes.
- final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
- if (enabled != null) {
- final int enabledCount = enabled.size();
- final String locale = mCurrentSubtype == null
- ? mRes.getConfiguration().locale.toString()
- : mCurrentSubtype.getLocale();
- for (int i = 0; i < enabledCount; ++i) {
- final InputMethodInfo imi = enabled.get(i);
- if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
- InputMethodSubtype keyboardSubtype =
- SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes,
- SubtypeUtils.getSubtypes(imi),
- SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
- if (keyboardSubtype != null) {
- targetLastImiId = imi.getId();
- subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
- keyboardSubtype.hashCode());
- if (keyboardSubtype.getLocale().equals(locale)) {
- break;
- }
- }
- }
- }
- }
- }
-
- if (!TextUtils.isEmpty(targetLastImiId)) {
- if (DEBUG) {
- Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
- + ", from: " + getSelectedMethodIdLocked() + ", " + subtypeId);
- }
- setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
- return true;
- } else {
- return false;
- }
- }
- }
-
- @BinderThread
- private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return false;
- }
- return switchToNextInputMethodLocked(token, onlyCurrentIme);
- }
- }
-
- @GuardedBy("ImfLock.class")
- private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
- final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype);
- if (nextSubtype == null) {
- return false;
- }
- setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
- nextSubtype.mSubtypeId);
- return true;
- }
-
- @BinderThread
- private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return false;
- }
- final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- false /* onlyCurrentIme */, mMethodMap.get(getSelectedMethodIdLocked()),
- mCurrentSubtype);
- return nextSubtype != null;
- }
- }
-
- @Override
- public InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
- synchronized (ImfLock.class) {
- if (mSettings.getCurrentUserId() == userId) {
- return mSettings.getLastInputMethodSubtypeLocked();
- }
-
- final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
- userId, false);
- return settings.getLastInputMethodSubtypeLocked();
- }
- }
-
- @Override
- public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
- @UserIdInt int userId) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
- final int callingUid = Binder.getCallingUid();
-
- // By this IPC call, only a process which shares the same uid with the IME can add
- // additional input method subtypes to the IME.
- if (TextUtils.isEmpty(imiId) || subtypes == null) return;
- final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>();
- for (InputMethodSubtype subtype : subtypes) {
- if (!toBeAdded.contains(subtype)) {
- toBeAdded.add(subtype);
- } else {
- Slog.w(TAG, "Duplicated subtype definition found: "
- + subtype.getLocale() + ", " + subtype.getMode());
- }
- }
- synchronized (ImfLock.class) {
- if (!mSystemReady) {
- return;
- }
-
- if (mSettings.getCurrentUserId() == userId) {
- if (!mSettings.setAdditionalInputMethodSubtypes(imiId, toBeAdded,
- mAdditionalSubtypeMap, mPackageManagerInternal, callingUid)) {
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return;
- }
-
- final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
- final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
- final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
- new ArrayMap<>();
- AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
- queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
- methodList, DirectBootAwareness.AUTO, mSettings.getEnabledInputMethodNames());
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
- userId, false);
- settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
- mPackageManagerInternal, callingUid);
- }
- }
-
- @Override
- public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
- @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
- final int callingUid = Binder.getCallingUid();
- final ComponentName imeComponentName =
- imeId != null ? ComponentName.unflattenFromString(imeId) : null;
- if (imeComponentName == null || !InputMethodUtils.checkIfPackageBelongsToUid(
- mPackageManagerInternal, callingUid, imeComponentName.getPackageName())) {
- throw new SecurityException("Calling UID=" + callingUid + " does not belong to imeId="
- + imeId);
- }
- Objects.requireNonNull(subtypeHashCodes, "subtypeHashCodes must not be null");
-
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (ImfLock.class) {
- final boolean currentUser = (mSettings.getCurrentUserId() == userId);
- final InputMethodSettings settings = currentUser
- ? mSettings
- : new InputMethodSettings(mContext, queryMethodMapForUser(userId), userId,
- !mUserManagerInternal.isUserUnlocked(userId));
- if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
- return;
- }
- if (currentUser) {
- // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
- if (mSettingsObserver != null) {
- mSettingsObserver.mLastEnabled = settings.getEnabledInputMethodsStr();
- }
- updateInputMethodsFromSettingsLocked(false /* enabledChanged */);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * This is kept due to {@code @UnsupportedAppUsage} in
- * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in
- * {@link InputMethodService#onCreate()}.
- *
- * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)}
- *
- * @deprecated TODO(b/113914148): Check if we can remove this
- */
- @Override
- @Deprecated
- public int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) {
- int callingUid = Binder.getCallingUid();
- return Binder.withCleanCallingIdentity(() -> {
- final int curTokenDisplayId;
- synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(callingUid, client,
- "getInputMethodWindowVisibleHeight", null /* statsToken */)) {
- if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
- EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
- mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
- }
- return 0;
- }
- // This should probably use the caller's display id, but because this is unsupported
- // and maintained only for compatibility, there's no point in fixing it.
- curTokenDisplayId = mCurTokenDisplayId;
- }
- return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId);
- });
- }
-
- @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
- @Override
- public void removeImeSurface() {
- super.removeImeSurface_enforcePermission();
-
- mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
- }
-
- @Override
- public void reportVirtualDisplayGeometryAsync(IInputMethodClient parentClient,
- int childDisplayId, float[] matrixValues) {
- final IInputMethodClientInvoker parentClientInvoker =
- IInputMethodClientInvoker.create(parentClient, mHandler);
- try {
- final DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(childDisplayId);
- if (displayInfo == null) {
- throw new IllegalArgumentException(
- "Cannot find display for non-existent displayId: " + childDisplayId);
- }
- final int callingUid = Binder.getCallingUid();
- if (callingUid != displayInfo.ownerUid) {
- throw new SecurityException("The caller doesn't own the display.");
- }
-
- synchronized (ImfLock.class) {
- final ClientState cs = mClients.get(parentClientInvoker.asBinder());
- if (cs == null) {
- return;
- }
-
- // null matrixValues means that the entry needs to be removed.
- if (matrixValues == null) {
- final VirtualDisplayInfo info =
- mVirtualDisplayIdToParentMap.get(childDisplayId);
- if (info == null) {
- return;
- }
- if (info.mParentClient != cs) {
- throw new SecurityException("Only the owner client can clear"
- + " VirtualDisplayGeometry for display #" + childDisplayId);
- }
- mVirtualDisplayIdToParentMap.remove(childDisplayId);
- return;
- }
-
- VirtualDisplayInfo info = mVirtualDisplayIdToParentMap.get(childDisplayId);
- if (info != null && info.mParentClient != cs) {
- throw new InvalidParameterException("Display #" + childDisplayId
- + " is already registered by " + info.mParentClient);
- }
- if (info == null) {
- if (!mWindowManagerInternal.isUidAllowedOnDisplay(childDisplayId, cs.mUid)) {
- throw new SecurityException(cs + " cannot access to display #"
- + childDisplayId);
- }
- info = new VirtualDisplayInfo(cs, new Matrix());
- mVirtualDisplayIdToParentMap.put(childDisplayId, info);
- }
- info.mMatrix.setValues(matrixValues);
-
- if (mCurClient == null || mCurClient.mCurSession == null) {
- return;
- }
-
- Matrix matrix = null;
- int displayId = mCurClient.mSelfReportedDisplayId;
- boolean needToNotify = false;
- while (true) {
- needToNotify |= (displayId == childDisplayId);
- final VirtualDisplayInfo next = mVirtualDisplayIdToParentMap.get(displayId);
- if (next == null) {
- break;
- }
- if (matrix == null) {
- matrix = new Matrix(next.mMatrix);
- } else {
- matrix.postConcat(next.mMatrix);
- }
- if (next.mParentClient.mSelfReportedDisplayId == mCurTokenDisplayId) {
- if (needToNotify) {
- final float[] values = new float[9];
- matrix.getValues(values);
- mCurClient.mClient.updateVirtualDisplayToScreenMatrix(
- getSequenceNumberLocked(), values);
- }
- break;
- }
- displayId = info.mParentClient.mSelfReportedDisplayId;
- }
- }
- } catch (Throwable t) {
- if (parentClientInvoker != null) {
- parentClientInvoker.throwExceptionFromSystem(t.toString());
- }
- }
- }
-
- @Override
- public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
- // No permission check, because we'll only execute the request if the calling window is
- // also the current IME client.
- mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
- }
-
- private void registerDeviceListenerAndCheckStylusSupport() {
- final InputManager im = mContext.getSystemService(InputManager.class);
- final IntArray stylusIds = getStylusInputDeviceIds(im);
- if (stylusIds.size() > 0) {
- synchronized (ImfLock.class) {
- mStylusIds = new IntArray();
- mStylusIds.addAll(stylusIds);
- }
- }
- im.registerInputDeviceListener(new InputManager.InputDeviceListener() {
- @Override
- public void onInputDeviceAdded(int deviceId) {
- InputDevice device = im.getInputDevice(deviceId);
- if (device != null && isStylusDevice(device)) {
- add(deviceId);
- }
- }
-
- @Override
- public void onInputDeviceRemoved(int deviceId) {
- remove(deviceId);
- }
-
- @Override
- public void onInputDeviceChanged(int deviceId) {
- InputDevice device = im.getInputDevice(deviceId);
- if (device == null) {
- return;
- }
- if (isStylusDevice(device)) {
- add(deviceId);
- } else {
- remove(deviceId);
- }
- }
-
- private void add(int deviceId) {
- synchronized (ImfLock.class) {
- addStylusDeviceIdLocked(deviceId);
- }
- }
-
- private void remove(int deviceId) {
- synchronized (ImfLock.class) {
- removeStylusDeviceIdLocked(deviceId);
- }
- }
- }, mHandler);
- }
-
- private void addStylusDeviceIdLocked(int deviceId) {
- if (mStylusIds == null) {
- mStylusIds = new IntArray();
- } else if (mStylusIds.indexOf(deviceId) != -1) {
- return;
- }
- Slog.d(TAG, "New Stylus deviceId" + deviceId + " added.");
- mStylusIds.add(deviceId);
- // a new Stylus is detected. If IME supports handwriting, and we don't have
- // handwriting initialized, lets do it now.
- if (!mHwController.getCurrentRequestId().isPresent()
- && mBindingController.supportsStylusHandwriting()) {
- scheduleResetStylusHandwriting();
- }
- }
-
- private void removeStylusDeviceIdLocked(int deviceId) {
- if (mStylusIds == null || mStylusIds.size() == 0) {
- return;
- }
- int index;
- if ((index = mStylusIds.indexOf(deviceId)) != -1) {
- mStylusIds.remove(index);
- Slog.d(TAG, "Stylus deviceId: " + deviceId + " removed.");
- }
- if (mStylusIds.size() == 0) {
- // no more supported stylus(es) in system.
- mHwController.reset();
- scheduleRemoveStylusHandwritingWindow();
- }
- }
-
- private static boolean isStylusDevice(InputDevice inputDevice) {
- return inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)
- || inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS);
- }
-
- @GuardedBy("ImfLock.class")
- private boolean hasSupportedStylusLocked() {
- return mStylusIds != null && mStylusIds.size() != 0;
- }
-
- /**
- * Helper method that adds a virtual stylus id for next handwriting session test if
- * a stylus deviceId is not already registered on device.
- */
- @BinderThread
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
- @Override
- public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
- super.addVirtualStylusIdForTestSession_enforcePermission();
-
- int uid = Binder.getCallingUid();
- synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
- null /* statsToken */)) {
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- if (DEBUG) Slog.v(TAG, "Adding virtual stylus id for session");
- addStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- /**
- * Helper method to set a stylus idle-timeout after which handwriting {@code InkWindow}
- * will be removed.
- * @param timeout to set in milliseconds. To reset to default, use a value <= zero.
- */
- @BinderThread
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
- @Override
- public void setStylusWindowIdleTimeoutForTest(
- IInputMethodClient client, @DurationMillisLong long timeout) {
- super.setStylusWindowIdleTimeoutForTest_enforcePermission();
-
- int uid = Binder.getCallingUid();
- synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
- null /* statsToken */)) {
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- if (DEBUG) Slog.v(TAG, "Setting stylus window idle timeout");
- getCurMethodLocked().setStylusWindowIdleTimeoutForTest(timeout);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void removeVirtualStylusIdForTestSessionLocked() {
- removeStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST);
- }
-
- private static IntArray getStylusInputDeviceIds(InputManager im) {
- IntArray stylusIds = new IntArray();
- for (int id : im.getInputDeviceIds()) {
- if (!im.isInputDeviceEnabled(id)) {
- continue;
- }
- InputDevice device = im.getInputDevice(id);
- if (device != null && isStylusDevice(device)) {
- stylusIds.add(id);
- }
- }
-
- return stylusIds;
- }
-
- /**
- * Starting point for dumping the IME tracing information in proto format.
- *
- * @param protoDump dump information from the IME client side
- */
- @BinderThread
- @Override
- public void startProtoDump(byte[] protoDump, int source, String where) {
- if (protoDump == null && source != ImeTracing.IME_TRACING_FROM_IMMS) {
- // Dump not triggered from IMMS, but no proto information provided.
- return;
- }
- ImeTracing tracingInstance = ImeTracing.getInstance();
- if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
- return;
- }
-
- ProtoOutputStream proto = new ProtoOutputStream();
- switch (source) {
- case ImeTracing.IME_TRACING_FROM_CLIENT:
- final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
- proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodClientsTraceProto.WHERE, where);
- proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
- proto.end(client_token);
- break;
- case ImeTracing.IME_TRACING_FROM_IMS:
- final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
- proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodServiceTraceProto.WHERE, where);
- proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
- proto.end(service_token);
- break;
- case ImeTracing.IME_TRACING_FROM_IMMS:
- final long managerservice_token =
- proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
- proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
- dumpDebug(proto,
- InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
- proto.end(managerservice_token);
- break;
- default:
- // Dump triggered by a source not recognised.
- return;
- }
- tracingInstance.addToBuffer(proto, source);
- }
-
- @BinderThread
- @Override
- public boolean isImeTraceEnabled() {
- return ImeTracing.getInstance().isEnabled();
- }
-
- @BinderThread
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
- @Override
- public void startImeTrace() {
- super.startImeTrace_enforcePermission();
-
- ImeTracing.getInstance().startTrace(null /* printwriter */);
- ArrayMap<IBinder, ClientState> clients;
- synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
- }
- for (ClientState state : clients.values()) {
- if (state != null) {
- state.mClient.setImeTraceEnabled(true /* enabled */);
- }
- }
- }
-
- @BinderThread
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
- @Override
- public void stopImeTrace() {
- super.stopImeTrace_enforcePermission();
-
- ImeTracing.getInstance().stopTrace(null /* printwriter */);
- ArrayMap<IBinder, ClientState> clients;
- synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
- }
- for (ClientState state : clients.values()) {
- if (state != null) {
- state.mClient.setImeTraceEnabled(false /* enabled */);
- }
- }
- }
-
- private void dumpDebug(ProtoOutputStream proto, long fieldId) {
- synchronized (ImfLock.class) {
- final long token = proto.start(fieldId);
- proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
- proto.write(CUR_SEQ, getSequenceNumberLocked());
- proto.write(CUR_CLIENT, Objects.toString(mCurClient));
- proto.write(CUR_FOCUSED_WINDOW_NAME,
- mWindowManagerInternal.getWindowName(mCurFocusedWindow));
- proto.write(LAST_IME_TARGET_WINDOW_NAME,
- mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
- proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
- InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
- if (mCurEditorInfo != null) {
- mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
- }
- proto.write(CUR_ID, getCurIdLocked());
- mVisibilityStateComputer.dumpDebug(proto, fieldId);
- proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
- proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
- proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
- proto.write(SYSTEM_READY, mSystemReady);
- proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
- proto.write(HAVE_CONNECTION, hasConnectionLocked());
- proto.write(BOUND_TO_METHOD, mBoundToMethod);
- proto.write(IS_INTERACTIVE, mIsInteractive);
- proto.write(BACK_DISPOSITION, mBackDisposition);
- proto.write(IME_WINDOW_VISIBILITY, mImeWindowVis);
- proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard());
- proto.end(token);
- }
- }
-
- @BinderThread
- private void notifyUserAction(@NonNull IBinder token) {
- if (DEBUG) {
- Slog.d(TAG, "Got the notification of a user action.");
- }
- synchronized (ImfLock.class) {
- if (getCurTokenLocked() != token) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
- + " active.");
- }
- return;
- }
- final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
- if (imi != null) {
- mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
- }
- }
- }
-
- @BinderThread
- private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
- @Nullable ImeTracker.Token statsToken) {
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
- return;
- }
- final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(
- windowToken);
- mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
- setVisible ? ImeVisibilityStateComputer.STATE_SHOW_IME
- : ImeVisibilityStateComputer.STATE_HIDE_IME);
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- @BinderThread
- private void resetStylusHandwriting(int requestId) {
- synchronized (ImfLock.class) {
- final OptionalInt curRequest = mHwController.getCurrentRequestId();
- if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) {
- Slog.w(TAG, "IME requested to finish handwriting with a mismatched requestId: "
- + requestId);
- }
- removeVirtualStylusIdForTestSessionLocked();
- scheduleResetStylusHandwriting();
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
- if (token == null) {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- "Using null token requires permission "
- + android.Manifest.permission.WRITE_SECURE_SETTINGS);
- }
- } else if (getCurTokenLocked() != token) {
- Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
- + " token: " + token);
- return;
- } else {
- // Called with current IME's token.
- if (mMethodMap.get(id) != null
- && mSettings.getEnabledInputMethodListWithFilterLocked(
- (info) -> info.getId().equals(id)).isEmpty()) {
- throw new IllegalStateException("Requested IME is not enabled: " + id);
- }
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- setInputMethodLocked(id, subtypeId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Called right after {@link IInputMethod#showSoftInput} or {@link IInputMethod#hideSoftInput}.
- */
- @GuardedBy("ImfLock.class")
- void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken,
- @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) {
- final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
- final WindowManagerInternal.ImeTargetInfo info =
- mWindowManagerInternal.onToggleImeRequested(
- show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
- mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
- mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
- info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
- info.imeSurfaceParentName));
-
- if (statsToken != null) {
- mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
- }
- }
-
- @BinderThread
- private void hideMySoftInput(@NonNull IBinder token, int flags,
- @SoftInputShowHideReason int reason) {
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
- null /* resultReceiver */, reason);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- @BinderThread
- private void showMySoftInput(@NonNull IBinder token, int flags) {
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
- null /* resultReceiver */,
- SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- @VisibleForTesting
- ImeVisibilityApplier getVisibilityApplier() {
- synchronized (ImfLock.class) {
- return mVisibilityApplier;
- }
- }
-
- void onApplyImeVisibilityFromComputer(IBinder windowToken,
- @NonNull ImeVisibilityResult result) {
- synchronized (ImfLock.class) {
- mVisibilityApplier.applyImeVisibility(windowToken, null, result.getState(),
- result.getReason());
- }
- }
-
- @GuardedBy("ImfLock.class")
- void setEnabledSessionLocked(SessionState session) {
- if (mEnabledSession != session) {
- if (mEnabledSession != null && mEnabledSession.mSession != null) {
- if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
- mEnabledSession.mMethod.setSessionEnabled(mEnabledSession.mSession, false);
- }
- mEnabledSession = session;
- if (mEnabledSession != null && mEnabledSession.mSession != null) {
- if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
- mEnabledSession.mMethod.setSessionEnabled(mEnabledSession.mSession, true);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- void setEnabledSessionForAccessibilityLocked(
- SparseArray<AccessibilitySessionState> accessibilitySessions) {
- // mEnabledAccessibilitySessions could the same object as accessibilitySessions.
- SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
- for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
- if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) {
- AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i);
- if (sessionState != null) {
- disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i),
- sessionState.mSession);
- }
- }
- }
- if (disabledSessions.size() > 0) {
- AccessibilityManagerInternal.get().setImeSessionEnabled(disabledSessions,
- false);
- }
- SparseArray<IAccessibilityInputMethodSession> enabledSessions = new SparseArray<>();
- for (int i = 0; i < accessibilitySessions.size(); i++) {
- if (!mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) {
- AccessibilitySessionState sessionState = accessibilitySessions.valueAt(i);
- if (sessionState != null) {
- enabledSessions.append(accessibilitySessions.keyAt(i), sessionState.mSession);
- }
- }
- }
- if (enabledSessions.size() > 0) {
- AccessibilityManagerInternal.get().setImeSessionEnabled(enabledSessions,
- true);
- }
- mEnabledAccessibilitySessions = accessibilitySessions;
- }
-
- @SuppressWarnings("unchecked")
- @UiThread
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SHOW_IM_SUBTYPE_PICKER:
- final boolean showAuxSubtypes;
- final int displayId = msg.arg2;
- switch (msg.arg1) {
- case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO:
- // This is undocumented so far, but IMM#showInputMethodPicker() has been
- // implemented so that auxiliary subtypes will be excluded when the soft
- // keyboard is invisible.
- synchronized (ImfLock.class) {
- showAuxSubtypes = isInputShown();
- }
- break;
- case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
- showAuxSubtypes = true;
- break;
- case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES:
- showAuxSubtypes = false;
- break;
- default:
- Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
- return false;
- }
- mMenuController.showInputMethodMenu(showAuxSubtypes, displayId);
- return true;
-
- // ---------------------------------------------------------
-
- case MSG_HIDE_CURRENT_INPUT_METHOD:
- synchronized (ImfLock.class) {
- final @SoftInputShowHideReason int reason = (int) msg.obj;
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
- null /* resultReceiver */, reason);
-
- }
- return true;
- case MSG_REMOVE_IME_SURFACE: {
- synchronized (ImfLock.class) {
- try {
- if (mEnabledSession != null && mEnabledSession.mSession != null
- && !isShowRequestedForCurrentWindow()) {
- mEnabledSession.mSession.removeImeSurface();
- }
- } catch (RemoteException e) {
- }
- }
- return true;
- }
- case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: {
- IBinder windowToken = (IBinder) msg.obj;
- synchronized (ImfLock.class) {
- try {
- if (windowToken == mCurFocusedWindow
- && mEnabledSession != null && mEnabledSession.mSession != null) {
- mEnabledSession.mSession.removeImeSurface();
- }
- } catch (RemoteException e) {
- }
- }
- return true;
- }
- case MSG_UPDATE_IME_WINDOW_STATUS: {
- updateImeWindowStatus(msg.arg1 == 1);
- return true;
- }
-
- // ---------------------------------------------------------
-
- case MSG_SET_INTERACTIVE:
- handleSetInteractive(msg.arg1 != 0);
- return true;
-
- // --------------------------------------------------------------
- case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
- mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
- synchronized (ImfLock.class) {
- sendOnNavButtonFlagsChangedLocked();
- }
- return true;
- case MSG_SYSTEM_UNLOCK_USER: {
- final int userId = msg.arg1;
- onUnlockUser(userId);
- return true;
- }
- case MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED: {
- final int userId = msg.arg1;
- final List<InputMethodInfo> imes = (List<InputMethodInfo>) msg.obj;
- mInputMethodListListeners.forEach(
- listener -> listener.onInputMethodListUpdated(imes, userId));
- return true;
- }
-
- // ---------------------------------------------------------------
- case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: {
- if (mAudioManagerInternal == null) {
- mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
- }
- if (mAudioManagerInternal != null) {
- mAudioManagerInternal.setInputMethodServiceUid(msg.arg1 /* uid */);
- }
- return true;
- }
-
- case MSG_RESET_HANDWRITING: {
- synchronized (ImfLock.class) {
- if (mBindingController.supportsStylusHandwriting()
- && getCurMethodLocked() != null && hasSupportedStylusLocked()) {
- Slog.d(TAG, "Initializing Handwriting Spy");
- mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
- } else {
- mHwController.reset();
- }
- }
- return true;
- }
- case MSG_PREPARE_HANDWRITING_DELEGATION:
- synchronized (ImfLock.class) {
- String delegate = (String) ((Pair) msg.obj).first;
- String delegator = (String) ((Pair) msg.obj).second;
- mHwController.prepareStylusHandwritingDelegation(delegate, delegator);
- }
- return true;
- case MSG_START_HANDWRITING:
- synchronized (ImfLock.class) {
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod == null || mCurFocusedWindow == null) {
- return true;
- }
- final HandwritingModeController.HandwritingSession session =
- mHwController.startHandwritingSession(
- msg.arg1 /*requestId*/,
- msg.arg2 /*pid*/,
- mBindingController.getCurMethodUid(),
- mCurFocusedWindow);
- if (session == null) {
- Slog.e(TAG,
- "Failed to start handwriting session for requestId: " + msg.arg1);
- return true;
- }
-
- if (!curMethod.startStylusHandwriting(session.getRequestId(),
- session.getHandwritingChannel(), session.getRecordedEvents())) {
- // When failed to issue IPCs, re-initialize handwriting state.
- Slog.w(TAG, "Resetting handwriting mode.");
- scheduleResetStylusHandwriting();
- }
- }
- return true;
- case MSG_FINISH_HANDWRITING:
- synchronized (ImfLock.class) {
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null && mHwController.getCurrentRequestId().isPresent()) {
- curMethod.finishStylusHandwriting();
- }
- }
- return true;
- case MSG_REMOVE_HANDWRITING_WINDOW:
- synchronized (ImfLock.class) {
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod != null) {
- curMethod.removeStylusHandwritingWindow();
- }
- }
- return true;
- }
- return false;
- }
-
- @BinderThread
- private void onStylusHandwritingReady(int requestId, int pid) {
- mHandler.obtainMessage(MSG_START_HANDWRITING, requestId, pid).sendToTarget();
- }
-
- private void handleSetInteractive(final boolean interactive) {
- synchronized (ImfLock.class) {
- mIsInteractive = interactive;
- updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition);
-
- // Inform the current client of the change in active status
- if (mCurClient == null || mCurClient.mClient == null) {
- return;
- }
- if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(getCurMethodUidLocked())) {
- // Handle IME visibility when interactive changed before finishing the input to
- // ensure we preserve the last state as possible.
- final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
- mCurFocusedWindow, interactive);
- if (imeVisRes != null) {
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null,
- imeVisRes.getState(), imeVisRes.getReason());
- }
- // Eligible IME processes use new "setInteractive" protocol.
- mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode);
- } else {
- // Legacy IME processes continue using legacy "setActive" protocol.
- mCurClient.mClient.setActive(mIsInteractive, mInFullscreenMode);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- private boolean chooseNewDefaultIMELocked() {
- final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodListLocked());
- if (imi != null) {
- if (DEBUG) {
- Slog.d(TAG, "New default IME was selected: " + imi.getId());
- }
- resetSelectedInputMethodAndSubtypeLocked(imi.getId());
- return true;
- }
-
- return false;
- }
-
- static void queryInputMethodServicesInternal(Context context,
- @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
- ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
- @DirectBootAwareness int directBootAwareness, List<String> enabledInputMethodList) {
- final Context userAwareContext = context.getUserId() == userId
- ? context
- : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
-
- methodList.clear();
- methodMap.clear();
-
- final int directBootAwarenessFlags;
- switch (directBootAwareness) {
- case DirectBootAwareness.ANY:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- break;
- case DirectBootAwareness.AUTO:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO;
- break;
- default:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO;
- Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness
- + ". Falling back to DirectBootAwareness.AUTO");
- break;
- }
- final int flags = PackageManager.GET_META_DATA
- | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | directBootAwarenessFlags;
- final List<ResolveInfo> services = userAwareContext.getPackageManager().queryIntentServices(
- new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.ResolveInfoFlags.of(flags));
-
- methodList.ensureCapacity(services.size());
- methodMap.ensureCapacity(services.size());
-
- filterInputMethodServices(additionalSubtypeMap, methodMap, methodList,
- enabledInputMethodList, userAwareContext, services);
- }
-
- static void filterInputMethodServices(
- ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
- ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
- List<String> enabledInputMethodList, Context userAwareContext,
- List<ResolveInfo> services) {
- final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>();
-
- for (int i = 0; i < services.size(); ++i) {
- ResolveInfo ri = services.get(i);
- ServiceInfo si = ri.serviceInfo;
- final String imeId = InputMethodInfo.computeId(ri);
- if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
- Slog.w(TAG, "Skipping input method " + imeId
- + ": it does not require the permission "
- + android.Manifest.permission.BIND_INPUT_METHOD);
- continue;
- }
-
- if (DEBUG) Slog.d(TAG, "Checking " + imeId);
-
- try {
- final InputMethodInfo imi = new InputMethodInfo(userAwareContext, ri,
- additionalSubtypeMap.get(imeId));
- if (imi.isVrOnly()) {
- continue; // Skip VR-only IME, which isn't supported for now.
- }
- final String packageName = si.packageName;
- // only include IMEs which are from the system, enabled, or below the threshold
- if (si.applicationInfo.isSystemApp() || enabledInputMethodList.contains(imi.getId())
- || imiPackageCount.getOrDefault(packageName, 0)
- < InputMethodInfo.MAX_IMES_PER_PACKAGE) {
- imiPackageCount.put(packageName,
- 1 + imiPackageCount.getOrDefault(packageName, 0));
-
- methodList.add(imi);
- methodMap.put(imi.getId(), imi);
- if (DEBUG) {
- Slog.d(TAG, "Found an input method " + imi);
- }
- } else if (DEBUG) {
- Slog.d(TAG, "Found an input method, but ignored due threshold: " + imi);
- }
- } catch (Exception e) {
- Slog.wtf(TAG, "Unable to load input method " + imeId, e);
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
- if (DEBUG) {
- Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
- + " \n ------ caller=" + Debug.getCallers(10));
- }
- if (!mSystemReady) {
- Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
- return;
- }
- mMethodMapUpdateCount++;
- mMyPackageMonitor.clearKnownImePackageNamesLocked();
-
- queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(),
- mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO,
- mSettings.getEnabledInputMethodNames());
-
- // Construct the set of possible IME packages for onPackageChanged() to avoid false
- // negatives when the package state remains to be the same but only the component state is
- // changed.
- {
- // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
- // of this query is to avoid false negatives. PackageManager.MATCH_ALL could be more
- // conservative, but it seems we cannot use it for now (Issue 35176630).
- final List<ResolveInfo> allInputMethodServices =
- mContext.getPackageManager().queryIntentServicesAsUser(
- new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId());
- final int numImes = allInputMethodServices.size();
- for (int i = 0; i < numImes; ++i) {
- final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
- if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
- mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName);
- }
- }
- }
-
- boolean reenableMinimumNonAuxSystemImes = false;
- // TODO: The following code should find better place to live.
- if (!resetDefaultEnabledIme) {
- boolean enabledImeFound = false;
- boolean enabledNonAuxImeFound = false;
- final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
- final int numImes = enabledImes.size();
- for (int i = 0; i < numImes; ++i) {
- final InputMethodInfo imi = enabledImes.get(i);
- if (mMethodList.contains(imi)) {
- enabledImeFound = true;
- if (!imi.isAuxiliaryIme()) {
- enabledNonAuxImeFound = true;
- break;
- }
- }
- }
- if (!enabledImeFound) {
- if (DEBUG) {
- Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
- }
- resetDefaultEnabledIme = true;
- resetSelectedInputMethodAndSubtypeLocked("");
- } else if (!enabledNonAuxImeFound) {
- if (DEBUG) {
- Slog.i(TAG, "All the enabled non-Aux IMEs are gone. Do partial reset.");
- }
- reenableMinimumNonAuxSystemImes = true;
- }
- }
-
- if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
- final ArrayList<InputMethodInfo> defaultEnabledIme =
- InputMethodInfoUtils.getDefaultEnabledImes(mContext, mMethodList,
- reenableMinimumNonAuxSystemImes);
- final int numImes = defaultEnabledIme.size();
- for (int i = 0; i < numImes; ++i) {
- final InputMethodInfo imi = defaultEnabledIme.get(i);
- if (DEBUG) {
- Slog.d(TAG, "--- enable ime = " + imi);
- }
- setInputMethodEnabledLocked(imi.getId(), true);
- }
- }
-
- final String defaultImiId = mSettings.getSelectedInputMethod();
- if (!TextUtils.isEmpty(defaultImiId)) {
- if (!mMethodMap.containsKey(defaultImiId)) {
- Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
- if (chooseNewDefaultIMELocked()) {
- updateInputMethodsFromSettingsLocked(true);
- }
- } else {
- // Double check that the default IME is certainly enabled.
- setInputMethodEnabledLocked(defaultImiId, true);
- }
- }
-
- updateDefaultVoiceImeIfNeededLocked();
-
- // Here is not the perfect place to reset the switching controller. Ideally
- // mSwitchingController and mSettings should be able to share the same state.
- // TODO: Make sure that mSwitchingController and mSettings are sharing the
- // the same enabled IMEs list.
- mSwitchingController.resetCircularListLocked(mContext);
- mHardwareKeyboardShortcutController.reset(mSettings);
-
- sendOnNavButtonFlagsChangedLocked();
-
- // Notify InputMethodListListeners of the new installed InputMethods.
- final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList);
- mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
- mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget();
- }
-
- @GuardedBy("ImfLock.class")
- void sendOnNavButtonFlagsChangedLocked() {
- final IInputMethodInvoker curMethod = mBindingController.getCurMethod();
- if (curMethod == null) {
- // No need to send the data if the IME is not yet bound.
- return;
- }
- curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked());
- }
-
- @GuardedBy("ImfLock.class")
- private void updateDefaultVoiceImeIfNeededLocked() {
- final String systemSpeechRecognizer =
- mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
- final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
- final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
- mMethodMap, systemSpeechRecognizer, currentDefaultVoiceImeId);
- if (newSystemVoiceIme == null) {
- if (DEBUG) {
- Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
- + " this may be expected.");
- }
- // Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings
- // does not update the actual Secure Settings until the user is unlocked.
- if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
- mSettings.putDefaultVoiceInputMethod("");
- // We don't support disabling the voice ime when a package is removed from the
- // config.
- }
- return;
- }
- if (TextUtils.equals(currentDefaultVoiceImeId, newSystemVoiceIme.getId())) {
- return;
- }
- if (DEBUG) {
- Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
- }
- setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
- mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
- }
-
- // ----------------------------------------------------------------------
-
- /**
- * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}.
- *
- * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently not
- * recognized by the system.
- * @param enabled {@code true} if {@code id} needs to be enabled.
- * @return {@code true} if the IME was previously enabled. {@code false} otherwise.
- */
- @GuardedBy("ImfLock.class")
- private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
- List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
- .getEnabledInputMethodsAndSubtypeListLocked();
-
- if (enabled) {
- for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
- if (pair.first.equals(id)) {
- // We are enabling this input method, but it is already enabled.
- // Nothing to do. The previous state was enabled.
- return true;
- }
- }
- mSettings.appendAndPutEnabledInputMethodLocked(id, false);
- // Previous state was disabled.
- return false;
- } else {
- StringBuilder builder = new StringBuilder();
- if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
- builder, enabledInputMethodsList, id)) {
- // Disabled input method is currently selected, switch to another one.
- final String selId = mSettings.getSelectedInputMethod();
- if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
- Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
- resetSelectedInputMethodAndSubtypeLocked("");
- }
- // Previous state was enabled.
- return true;
- } else {
- // We are disabling the input method but it is already disabled.
- // Nothing to do. The previous state was disabled.
- return false;
- }
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
- boolean setSubtypeOnly) {
- mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
- mCurrentSubtype);
-
- // Set Subtype here
- if (imi == null || subtypeId < 0) {
- mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
- mCurrentSubtype = null;
- } else {
- if (subtypeId < imi.getSubtypeCount()) {
- InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
- mSettings.putSelectedSubtype(subtype.hashCode());
- mCurrentSubtype = subtype;
- } else {
- mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
- // If the subtype is not specified, choose the most applicable one
- mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
- }
- }
- notifyInputMethodSubtypeChangedLocked(mSettings.getCurrentUserId(), imi, mCurrentSubtype);
-
- if (!setSubtypeOnly) {
- // Set InputMethod here
- mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
- }
- }
-
- @GuardedBy("ImfLock.class")
- private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
- InputMethodInfo imi = mMethodMap.get(newDefaultIme);
- int lastSubtypeId = NOT_A_SUBTYPE_ID;
- // newDefaultIme is empty when there is no candidate for the selected IME.
- if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
- String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
- if (subtypeHashCode != null) {
- try {
- lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
- Integer.parseInt(subtypeHashCode));
- } catch (NumberFormatException e) {
- Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
- }
- }
- }
- setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
- }
-
- /**
- * Gets the current subtype of this input method.
- *
- * @param userId User ID to be queried about.
- * @return The current {@link InputMethodSubtype} for the specified user.
- */
- @Nullable
- @Override
- public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
- if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
- }
- synchronized (ImfLock.class) {
- if (mSettings.getCurrentUserId() == userId) {
- return getCurrentInputMethodSubtypeLocked();
- }
-
- final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
- userId, false);
- return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
- }
- }
-
- /**
- * Returns the current {@link InputMethodSubtype} for the current user.
- *
- * <p>CAVEATS: You must also update
- * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}
- * when you update the algorithm of this method.</p>
- *
- * <p>TODO: Address code duplication between this and
- * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}.</p>
- */
- @GuardedBy("ImfLock.class")
- InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
- String selectedMethodId = getSelectedMethodIdLocked();
- if (selectedMethodId == null) {
- return null;
- }
- final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
- final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
- if (imi == null || imi.getSubtypeCount() == 0) {
- return null;
- }
- if (!subtypeIsSelected || mCurrentSubtype == null
- || !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
- int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
- if (subtypeId == NOT_A_SUBTYPE_ID) {
- // If there are no selected subtypes, the framework will try to find
- // the most applicable subtype from explicitly or implicitly enabled
- // subtypes.
- List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
- // If there is only one explicitly or implicitly enabled subtype,
- // just returns it.
- if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
- mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
- } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
- mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
- mRes, explicitlyOrImplicitlyEnabledSubtypes,
- SubtypeUtils.SUBTYPE_MODE_KEYBOARD, null, true);
- if (mCurrentSubtype == null) {
- mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
- mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, true);
- }
- }
- } else {
- mCurrentSubtype = SubtypeUtils.getSubtypes(imi).get(subtypeId);
- }
- }
- 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<>();
- final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
- new ArrayMap<>();
- AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
- queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- methodMap, methodList, DirectBootAwareness.AUTO,
- mSettings.getEnabledInputMethodNames());
- return methodMap;
- }
-
- @GuardedBy("ImfLock.class")
- private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
- if (userId == mSettings.getCurrentUserId()) {
- if (!mMethodMap.containsKey(imeId)
- || !mSettings.getEnabledInputMethodListLocked()
- .contains(mMethodMap.get(imeId))) {
- return false; // IME is not found or not enabled.
- }
- setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
- return true;
- }
- final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
- false);
- if (!methodMap.containsKey(imeId)
- || !settings.getEnabledInputMethodListLocked().contains(methodMap.get(imeId))) {
- return false; // IME is not found or not enabled.
- }
- settings.putSelectedInputMethod(imeId);
- settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
- return true;
- }
-
- /**
- * Filter the access to the input method by rules of the package visibility. Return {@code true}
- * if the given input method is the currently selected one or visible to the caller.
- *
- * @param targetPkgName The package name of input method to check.
- * @param callingUid The caller that is going to access the input method.
- * @param userId The user ID where the input method resides.
- * @param settings The input method settings under the given user ID.
- * @return {@code true} if caller is able to access the input method.
- */
- private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid,
- @UserIdInt int userId, @NonNull InputMethodSettings settings) {
- final String methodId = settings.getSelectedInputMethod();
- final ComponentName selectedInputMethod = methodId != null
- ? InputMethodUtils.convertIdToComponentName(methodId) : null;
- if (selectedInputMethod != null
- && selectedInputMethod.getPackageName().equals(targetPkgName)) {
- return true;
- }
- final boolean canAccess = !mPackageManagerInternal.filterAppAccess(
- targetPkgName, callingUid, userId);
- if (DEBUG && !canAccess) {
- Slog.d(TAG, "Input method " + targetPkgName
- + " is not visible to the caller " + callingUid);
- }
- return canAccess;
- }
-
- // Start CarInputMethodManagerService
- private void publishLocalService() {
- LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
- }
-
- void notifySystemUnlockUser(@UserIdInt int userId) {
- mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, userId, 0).sendToTarget();
- }
- // End CarInputMethodManagerService
-
- private final class LocalServiceImpl extends InputMethodManagerInternal {
-
- @Override
- public void setInteractive(boolean interactive) {
- // Do everything in handler so as not to block the caller.
- mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0).sendToTarget();
- }
-
- @Override
- public void hideCurrentInputMethod(@SoftInputShowHideReason int reason) {
- mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
- mHandler.obtainMessage(MSG_HIDE_CURRENT_INPUT_METHOD, reason).sendToTarget();
- }
-
- @Override
- public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
- synchronized (ImfLock.class) {
- return getInputMethodListLocked(userId, DirectBootAwareness.AUTO,
- Process.SYSTEM_UID);
- }
- }
-
- @Override
- public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
- synchronized (ImfLock.class) {
- return getEnabledInputMethodListLocked(userId, Process.SYSTEM_UID);
- }
- }
-
- @Override
- public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
- InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
- // Get the device global touch exploration state before lock to avoid deadlock.
- final boolean touchExplorationEnabled = AccessibilityManagerInternal.get()
- .isTouchExplorationEnabled(userId);
-
- synchronized (ImfLock.class) {
- mAutofillController.onCreateInlineSuggestionsRequest(userId, requestInfo, cb,
- touchExplorationEnabled);
- }
- }
-
- @Override
- public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
- synchronized (ImfLock.class) {
- return switchToInputMethodLocked(imeId, userId);
- }
- }
-
- @Override
- public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
- synchronized (ImfLock.class) {
- if (userId == mSettings.getCurrentUserId()) {
- if (!mMethodMap.containsKey(imeId)) {
- return false; // IME is not found.
- }
- setInputMethodEnabledLocked(imeId, enabled);
- return true;
- }
- final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
- userId, false);
- if (!methodMap.containsKey(imeId)) {
- return false; // IME is not found.
- }
- if (enabled) {
- if (!settings.getEnabledInputMethodListLocked().contains(
- methodMap.get(imeId))) {
- settings.appendAndPutEnabledInputMethodLocked(imeId, false);
- }
- } else {
- settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
- new StringBuilder(),
- settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
- }
- return true;
- }
- }
-
- @Override
- public void registerInputMethodListListener(InputMethodListListener listener) {
- mInputMethodListListeners.addIfAbsent(listener);
- }
-
- @Override
- public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
- int displayId) {
- //TODO(b/150843766): Check if Input Token is valid.
- final IBinder curHostInputToken;
- synchronized (ImfLock.class) {
- if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) {
- return false;
- }
- curHostInputToken = mCurHostInputToken;
- }
- return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken);
- }
-
- @Override
- public void reportImeControl(@Nullable IBinder windowToken) {
- synchronized (ImfLock.class) {
- if (mCurFocusedWindow != windowToken) {
- // mCurPerceptible was set by the focused window, but it is no longer in
- // control, so we reset mCurPerceptible.
- mCurPerceptible = true;
- }
- }
- }
-
- @Override
- public void onImeParentChanged() {
- synchronized (ImfLock.class) {
- // Hide the IME method menu only when the IME surface parent is changed by the
- // input target changed, in case seeing the dialog dismiss flickering during
- // the next focused window starting the input connection.
- if (mLastImeTargetWindow != mCurFocusedWindow) {
- mMenuController.hideInputMethodMenuLocked();
- }
- }
- }
-
- @Override
- public void removeImeSurface() {
- mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
- }
-
- @Override
- public void updateImeWindowStatus(boolean disableImeIcon) {
- mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
- .sendToTarget();
- }
-
- @Override
- public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
- IAccessibilityInputMethodSession session) {
- synchronized (ImfLock.class) {
- if (mCurClient != null) {
- clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
- mCurClient.mAccessibilitySessions.put(accessibilityConnectionId,
- new AccessibilitySessionState(mCurClient, accessibilityConnectionId,
- session));
-
- attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY,
- true);
-
- final SessionState sessionState = mCurClient.mCurSession;
- final IInputMethodSession imeSession = sessionState == null
- ? null : sessionState.mSession;
- final SparseArray<IAccessibilityInputMethodSession>
- accessibilityInputMethodSessions =
- createAccessibilityInputMethodSessions(
- mCurClient.mAccessibilitySessions);
- final InputBindResult res = new InputBindResult(
- InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
- imeSession, accessibilityInputMethodSessions, null, getCurIdLocked(),
- getSequenceNumberLocked(), mCurVirtualDisplayToScreenMatrix, false);
- mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId);
- }
- }
- }
-
- @Override
- public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) {
- synchronized (ImfLock.class) {
- if (mCurClient != null) {
- if (DEBUG) {
- Slog.v(TAG, "unbindAccessibilityFromCurrentClientLocked: client="
- + mCurClient.mClient.asBinder());
- }
- // A11yManagerService unbinds the disabled accessibility service. We don't need
- // to do it here.
- mCurClient.mClient.onUnbindAccessibilityService(getSequenceNumberLocked(),
- accessibilityConnectionId);
- }
- // We only have sessions when we bound to an input method. Remove this session
- // from all clients.
- if (getCurMethodLocked() != null) {
- final int numClients = mClients.size();
- for (int i = 0; i < numClients; ++i) {
- clearClientSessionForAccessibilityLocked(mClients.valueAt(i),
- accessibilityConnectionId);
- }
- AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
- accessibilityConnectionId);
- if (session != null) {
- finishSessionForAccessibilityLocked(session);
- mEnabledAccessibilitySessions.remove(accessibilityConnectionId);
- }
- }
- }
- }
-
- @Override
- public void maybeFinishStylusHandwriting() {
- mHandler.removeMessages(MSG_FINISH_HANDWRITING);
- mHandler.obtainMessage(MSG_FINISH_HANDWRITING).sendToTarget();
- }
-
- @Override
- public void switchKeyboardLayout(int direction) {
- synchronized (ImfLock.class) {
- final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
- if (currentImi == null) {
- return;
- }
- final InputMethodSubtypeHandle currentSubtypeHandle =
- InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
- final InputMethodSubtypeHandle nextSubtypeHandle =
- mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
- direction > 0);
- if (nextSubtypeHandle == null) {
- return;
- }
- final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
- if (nextImi == null) {
- return;
- }
-
- final int subtypeCount = nextImi.getSubtypeCount();
- if (subtypeCount == 0) {
- if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
- setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
- }
- return;
- }
-
- for (int i = 0; i < subtypeCount; ++i) {
- if (nextSubtypeHandle.equals(
- InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
- setInputMethodLocked(nextImi.getId(), i);
- return;
- }
- }
- }
- }
- }
-
- @BinderThread
- private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
- @Nullable Uri contentUri, @Nullable String packageName) {
- if (token == null) {
- throw new NullPointerException("token");
- }
- if (packageName == null) {
- throw new NullPointerException("packageName");
- }
- if (contentUri == null) {
- throw new NullPointerException("contentUri");
- }
- final String contentUriScheme = contentUri.getScheme();
- if (!"content".equals(contentUriScheme)) {
- throw new InvalidParameterException("contentUri must have content scheme");
- }
-
- synchronized (ImfLock.class) {
- final int uid = Binder.getCallingUid();
- if (getSelectedMethodIdLocked() == null) {
- return null;
- }
- if (getCurTokenLocked() != token) {
- Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurTokenLocked()
- + " token=" + token);
- return null;
- }
- // We cannot simply distinguish a bad IME that reports an arbitrary package name from
- // an unfortunate IME whose internal state is already obsolete due to the asynchronous
- // nature of our system. Let's compare it with our internal record.
- final String curPackageName = mCurEditorInfo != null
- ? mCurEditorInfo.packageName : null;
- if (!TextUtils.equals(curPackageName, packageName)) {
- Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
- + curPackageName + " packageName=" + packageName);
- return null;
- }
- // This user ID can never bee spoofed.
- final int imeUserId = UserHandle.getUserId(uid);
- // This user ID can never bee spoofed.
- final int appUserId = UserHandle.getUserId(mCurClient.mUid);
- // This user ID may be invalid if "contentUri" embedded an invalid user ID.
- final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
- imeUserId);
- final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
- // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
- // actually has the right to grant a read permission for "contentUriWithoutUserId" that
- // is claimed to belong to "contentUriOwnerUserId". For example, specifying random
- // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
- // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
- // actually allowed to "uid", which is guaranteed to be the IME's one.
- return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
- packageName, contentUriOwnerUserId, appUserId);
- }
- }
-
- @BinderThread
- private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token)) {
- return;
- }
- if (mCurClient != null && mCurClient.mClient != null) {
- mInFullscreenMode = fullscreen;
- mCurClient.mClient.reportFullscreenMode(fullscreen);
- }
- }
- }
-
- private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
- /**
- * {@inheritDoc}
- */
- @BinderThread
- @Override
- public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
- boolean asProto) {
- if (asProto) {
- dumpAsProtoNoCheck(fd);
- } else {
- dumpAsStringNoCheck(fd, pw, args, true /* isCritical */);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @BinderThread
- @Override
- public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
- dumpNormal(fd, pw, args, asProto);
- }
-
- /**
- * {@inheritDoc}
- */
- @BinderThread
- @Override
- public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
- if (asProto) {
- dumpAsProtoNoCheck(fd);
- } else {
- dumpAsStringNoCheck(fd, pw, args, false /* isCritical */);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @BinderThread
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
- dumpNormal(fd, pw, args, asProto);
- }
-
- @BinderThread
- private void dumpAsProtoNoCheck(FileDescriptor fd) {
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
- dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
- proto.flush();
- }
- };
-
- @BinderThread
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
- PriorityDump.dump(mPriorityDumper, fd, pw, args);
- }
-
- @BinderThread
- private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
- boolean isCritical) {
- IInputMethodInvoker method;
- ClientState client;
- ClientState focusedWindowClient;
-
- final Printer p = new PrintWriterPrinter(pw);
-
- synchronized (ImfLock.class) {
- p.println("Current Input Method Manager state:");
- int numImes = mMethodList.size();
- p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
- for (int i = 0; i < numImes; i++) {
- InputMethodInfo info = mMethodList.get(i);
- p.println(" InputMethod #" + i + ":");
- info.dump(p, " ");
- }
- p.println(" Clients:");
- final int numClients = mClients.size();
- for (int i = 0; i < numClients; ++i) {
- final ClientState ci = mClients.valueAt(i);
- p.println(" Client " + ci + ":");
- p.println(" client=" + ci.mClient);
- p.println(" fallbackInputConnection=" + ci.mFallbackInputConnection);
- p.println(" sessionRequested=" + ci.mSessionRequested);
- p.println(" sessionRequestedForAccessibility="
- + ci.mSessionRequestedForAccessibility);
- p.println(" curSession=" + ci.mCurSession);
- }
- p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
- client = mCurClient;
- p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
- p.println(" mCurPerceptible=" + mCurPerceptible);
- p.println(" mCurFocusedWindow=" + mCurFocusedWindow
- + " softInputMode="
- + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)
- + " client=" + mCurFocusedWindowClient);
- focusedWindowClient = mCurFocusedWindowClient;
- p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
- + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
- + mBindingController.isVisibleBound());
- p.println(" mCurToken=" + getCurTokenLocked());
- p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId);
- p.println(" mCurHostInputToken=" + mCurHostInputToken);
- p.println(" mCurIntent=" + getCurIntentLocked());
- method = getCurMethodLocked();
- p.println(" mCurMethod=" + getCurMethodLocked());
- p.println(" mEnabledSession=" + mEnabledSession);
- mVisibilityStateComputer.dump(pw);
- p.println(" mInFullscreenMode=" + mInFullscreenMode);
- p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
- p.println(" mSettingsObserver=" + mSettingsObserver);
- p.println(" mStylusIds=" + (mStylusIds != null
- ? Arrays.toString(mStylusIds.toArray()) : ""));
- p.println(" mSwitchingController:");
- mSwitchingController.dump(p);
- p.println(" mSettings:");
- mSettings.dumpLocked(p, " ");
-
- p.println(" mStartInputHistory:");
- mStartInputHistory.dump(pw, " ");
-
- p.println(" mSoftInputShowHideHistory:");
- mSoftInputShowHideHistory.dump(pw, " ");
-
- p.println(" mImeTrackerService#History:");
- mImeTrackerService.dump(pw, " ");
- }
-
- // Exit here for critical dump, as remaining sections require IPCs to other processes.
- if (isCritical) {
- return;
- }
-
- p.println(" ");
- if (client != null) {
- pw.flush();
- try {
- TransferPipe.dumpAsync(client.mClient.asBinder(), fd, args);
- } catch (IOException | RemoteException e) {
- p.println("Failed to dump input method client: " + e);
- }
- } else {
- p.println("No input method client.");
- }
-
- if (focusedWindowClient != null && client != focusedWindowClient) {
- p.println(" ");
- p.println("Warning: Current input method client doesn't match the last focused. "
- + "window.");
- p.println("Dumping input method client in the last focused window just in case.");
- p.println(" ");
- pw.flush();
- try {
- TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args);
- } catch (IOException | RemoteException e) {
- p.println("Failed to dump input method client in focused window: " + e);
- }
- }
-
- p.println(" ");
- if (method != null) {
- pw.flush();
- try {
- TransferPipe.dumpAsync(method.asBinder(), fd, args);
- } catch (IOException | RemoteException e) {
- p.println("Failed to dump input method service: " + e);
- }
- } else {
- p.println("No input method service.");
- }
- }
-
- @BinderThread
- @Override
- public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
- @Nullable FileDescriptor err,
- @NonNull String[] args, @Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
- final int callingUid = Binder.getCallingUid();
- // Reject any incoming calls from non-shell users, including ones from the system user.
- if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
- // Note that Binder#onTransact() will automatically close "in", "out", and "err" when
- // returned from this method, hence there is no need to close those FDs.
- // "resultReceiver" is the only thing that needs to be taken care of here.
- if (resultReceiver != null) {
- resultReceiver.send(ShellCommandResult.FAILURE, null);
- }
- final String errorMsg = "InputMethodManagerService does not support shell commands from"
- + " non-shell users. callingUid=" + callingUid
- + " args=" + Arrays.toString(args);
- if (Process.isCoreUid(callingUid)) {
- // Let's not crash the calling process if the caller is one of core components.
- Slog.e(TAG, errorMsg);
- return;
- }
- throw new SecurityException(errorMsg);
- }
- new ShellCommandImpl(this).exec(
- this, in, out, err, args, callback, resultReceiver);
- }
-
- private static final class ShellCommandImpl extends ShellCommand {
- // Start CarInputMethodManagerService
- @NonNull
- final CarInputMethodManagerService mService;
-
- ShellCommandImpl(CarInputMethodManagerService service) {
- mService = service;
- }
- // End CarInputMethodManagerService
-
- @BinderThread
- @ShellCommandResult
- @Override
- public int onCommand(@Nullable String cmd) {
- final long identity = Binder.clearCallingIdentity();
- try {
- return onCommandWithSystemIdentity(cmd);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @BinderThread
- @ShellCommandResult
- private int onCommandWithSystemIdentity(@Nullable String cmd) {
- switch (TextUtils.emptyIfNull(cmd)) {
- case "get-last-switch-user-id":
- return mService.getLastSwitchUserId(this);
- case "tracing":
- return mService.handleShellCommandTraceInputMethod(this);
- case "ime": { // For "adb shell ime <command>".
- final String imeCommand = TextUtils.emptyIfNull(getNextArg());
- switch (imeCommand) {
- case "":
- case "-h":
- case "help":
- return onImeCommandHelp();
- case "list":
- return mService.handleShellCommandListInputMethods(this);
- case "enable":
- return mService.handleShellCommandEnableDisableInputMethod(this, true);
- case "disable":
- return mService.handleShellCommandEnableDisableInputMethod(this, false);
- case "set":
- return mService.handleShellCommandSetInputMethod(this);
- case "reset":
- return mService.handleShellCommandResetInputMethod(this);
- case "tracing": // TODO(b/180765389): Unsupport "adb shell ime tracing"
- return mService.handleShellCommandTraceInputMethod(this);
- default:
- getOutPrintWriter().println("Unknown command: " + imeCommand);
- return ShellCommandResult.FAILURE;
- }
- }
- default:
- return handleDefaultCommands(cmd);
- }
- }
-
- @BinderThread
- @Override
- public void onHelp() {
- try (PrintWriter pw = getOutPrintWriter()) {
- pw.println("CarInputMethodManagerService commands:");
- pw.println(" help");
- pw.println(" Prints this help text.");
- pw.println(" dump [options]");
- pw.println(" Synonym of dumpsys.");
- pw.println(" ime <command> [options]");
- pw.println(" Manipulate IMEs. Run \"ime help\" for details.");
- pw.println(" tracing <command>");
- pw.println(" start: Start tracing.");
- pw.println(" stop : Stop tracing.");
- pw.println(" help : Show help.");
- }
- }
-
- @BinderThread
- @ShellCommandResult
- private int onImeCommandHelp() {
- try (IndentingPrintWriter pw =
- new IndentingPrintWriter(getOutPrintWriter(), " ", 100)) {
- pw.println("ime <command>:");
- pw.increaseIndent();
-
- pw.println("list [-a] [-s]");
- pw.increaseIndent();
- pw.println("prints all enabled input methods.");
- pw.increaseIndent();
- pw.println("-a: see all input methods");
- pw.println("-s: only a single summary line of each");
- pw.decreaseIndent();
- pw.decreaseIndent();
-
- pw.println("enable [--user <USER_ID>] <ID>");
- pw.increaseIndent();
- pw.println("allows the given input method ID to be used.");
- pw.increaseIndent();
- pw.print("--user <USER_ID>: Specify which user to enable.");
- pw.println(" Assumes the current user if not specified.");
- pw.decreaseIndent();
- pw.decreaseIndent();
-
- pw.println("disable [--user <USER_ID>] <ID>");
- pw.increaseIndent();
- pw.println("disallows the given input method ID to be used.");
- pw.increaseIndent();
- pw.print("--user <USER_ID>: Specify which user to disable.");
- pw.println(" Assumes the current user if not specified.");
- pw.decreaseIndent();
- pw.decreaseIndent();
-
- pw.println("set [--user <USER_ID>] <ID>");
- pw.increaseIndent();
- pw.println("switches to the given input method ID.");
- pw.increaseIndent();
- pw.print("--user <USER_ID>: Specify which user to enable.");
- pw.println(" Assumes the current user if not specified.");
- pw.decreaseIndent();
- pw.decreaseIndent();
-
- pw.println("reset [--user <USER_ID>]");
- pw.increaseIndent();
- pw.println("reset currently selected/enabled IMEs to the default ones as if "
- + "the device is initially booted with the current locale.");
- pw.increaseIndent();
- pw.print("--user <USER_ID>: Specify which user to reset.");
- pw.println(" Assumes the current user if not specified.");
- pw.decreaseIndent();
-
- pw.decreaseIndent();
-
- pw.decreaseIndent();
- }
- return ShellCommandResult.SUCCESS;
- }
- }
-
- // ----------------------------------------------------------------------
- // Shell command handlers:
-
- @BinderThread
- @ShellCommandResult
- private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) {
- synchronized (ImfLock.class) {
- shellCommand.getOutPrintWriter().println(mLastSwitchUserId);
- return ShellCommandResult.SUCCESS;
- }
- }
-
- /**
- * Handles {@code adb shell ime list}.
- * @param shellCommand {@link ShellCommand} object that is handling this command.
- * @return Exit code of the command.
- */
- @BinderThread
- @ShellCommandResult
- private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) {
- boolean all = false;
- boolean brief = false;
- int userIdToBeResolved = UserHandle.USER_CURRENT;
- while (true) {
- final String nextOption = shellCommand.getNextOption();
- if (nextOption == null) {
- break;
- }
- switch (nextOption) {
- case "-a":
- all = true;
- break;
- case "-s":
- brief = true;
- break;
- case "-u":
- case "--user":
- userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired());
- break;
- }
- }
- synchronized (ImfLock.class) {
- final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
- try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
- for (int userId : userIds) {
- final List<InputMethodInfo> methods = all
- ? getInputMethodListLocked(
- userId, DirectBootAwareness.AUTO, Process.SHELL_UID)
- : getEnabledInputMethodListLocked(userId, Process.SHELL_UID);
- if (userIds.length > 1) {
- pr.print("User #");
- pr.print(userId);
- pr.println(":");
- }
- for (InputMethodInfo info : methods) {
- if (brief) {
- pr.println(info.getId());
- } else {
- pr.print(info.getId());
- pr.println(":");
- info.dump(pr::println, " ");
- }
- }
- }
- }
- }
- return ShellCommandResult.SUCCESS;
- }
-
- /**
- * Handles {@code adb shell ime enable} and {@code adb shell ime disable}.
- *
- * @param shellCommand {@link ShellCommand} object that is handling this command.
- * @param enabled {@code true} if the command was {@code adb shell ime enable}.
- * @return Exit code of the command.
- */
- @BinderThread
- @ShellCommandResult
- private int handleShellCommandEnableDisableInputMethod(
- @NonNull ShellCommand shellCommand, boolean enabled) {
- final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand);
- final String imeId = shellCommand.getNextArgRequired();
- boolean hasFailed = false;
- try (PrintWriter out = shellCommand.getOutPrintWriter();
- PrintWriter error = shellCommand.getErrPrintWriter()) {
- synchronized (ImfLock.class) {
- final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
- for (int userId : userIds) {
- if (!userHasDebugPriv(userId, shellCommand)) {
- continue;
- }
- hasFailed |= !handleShellCommandEnableDisableInputMethodInternalLocked(
- userId, imeId, enabled, out, error);
- }
- }
- }
- return hasFailed ? ShellCommandResult.FAILURE : ShellCommandResult.SUCCESS;
- }
-
- /**
- * A special helper method for commands that only have {@code -u} and {@code --user} options.
- *
- * <p>You cannot use this helper method if the command has other options.</p>
- *
- * <p>CAVEAT: This method must be called only once before any other
- * {@link ShellCommand#getNextArg()} and {@link ShellCommand#getNextArgRequired()} for the
- * main arguments.</p>
- *
- * @param shellCommand {@link ShellCommand} from which options should be obtained.
- * @return User ID to be resolved. {@link UserHandle#CURRENT} if not specified.
- */
- @BinderThread
- @UserIdInt
- private static int handleOptionsForCommandsThatOnlyHaveUserOption(ShellCommand shellCommand) {
- while (true) {
- final String nextOption = shellCommand.getNextOption();
- if (nextOption == null) {
- break;
- }
- switch (nextOption) {
- case "-u":
- case "--user":
- return UserHandle.parseUserArg(shellCommand.getNextArgRequired());
- }
- }
- return UserHandle.USER_CURRENT;
- }
-
- /**
- * Handles core logic of {@code adb shell ime enable} and {@code adb shell ime disable}.
- *
- * @param userId user ID specified to the command. Pseudo user IDs are not supported.
- * @param imeId IME ID specified to the command.
- * @param enabled {@code true} for {@code adb shell ime enable}. {@code false} otherwise.
- * @param out {@link PrintWriter} to output standard messages.
- * @param error {@link PrintWriter} to output error messages.
- * @return {@code false} if it fails to enable the IME. {@code false} otherwise.
- */
- @BinderThread
- @GuardedBy("ImfLock.class")
- private boolean handleShellCommandEnableDisableInputMethodInternalLocked(
- @UserIdInt int userId, String imeId, boolean enabled, PrintWriter out,
- PrintWriter error) {
- boolean failedToEnableUnknownIme = false;
- boolean previouslyEnabled = false;
- if (userId == mSettings.getCurrentUserId()) {
- if (enabled && !mMethodMap.containsKey(imeId)) {
- failedToEnableUnknownIme = true;
- } else {
- previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
- }
- } else {
- final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
- userId, false);
- if (enabled) {
- if (!methodMap.containsKey(imeId)) {
- failedToEnableUnknownIme = true;
- } else {
- for (InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
- if (TextUtils.equals(imi.getId(), imeId)) {
- previouslyEnabled = true;
- break;
- }
- }
- if (!previouslyEnabled) {
- settings.appendAndPutEnabledInputMethodLocked(imeId, false);
- }
- }
- } else {
- previouslyEnabled =
- settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
- new StringBuilder(),
- settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
- }
- }
- if (failedToEnableUnknownIme) {
- error.print("Unknown input method ");
- error.print(imeId);
- error.println(" cannot be enabled for user #" + userId);
- // Also print this failure into logcat for better debuggability.
- Slog.e(TAG, "\"ime enable " + imeId + "\" for user #" + userId
- + " failed due to its unrecognized IME ID.");
- return false;
- }
- out.print("Input method ");
- out.print(imeId);
- out.print(": ");
- out.print((enabled == previouslyEnabled) ? "already " : "now ");
- out.print(enabled ? "enabled" : "disabled");
- out.print(" for user #");
- out.println(userId);
- return true;
- }
-
- /**
- * Handles {@code adb shell ime set}.
- *
- * @param shellCommand {@link ShellCommand} object that is handling this command.
- * @return Exit code of the command.
- */
- @BinderThread
- @ShellCommandResult
- private int handleShellCommandSetInputMethod(@NonNull ShellCommand shellCommand) {
- final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand);
- final String imeId = shellCommand.getNextArgRequired();
- boolean hasFailed = false;
- try (PrintWriter out = shellCommand.getOutPrintWriter();
- PrintWriter error = shellCommand.getErrPrintWriter()) {
- synchronized (ImfLock.class) {
- final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
- for (int userId : userIds) {
- if (!userHasDebugPriv(userId, shellCommand)) {
- continue;
- }
- boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId,
- userId);
- if (failedToSelectUnknownIme) {
- error.print("Unknown input method ");
- error.print(imeId);
- error.print(" cannot be selected for user #");
- error.println(userId);
- // Also print this failure into logcat for better debuggability.
- Slog.e(TAG, "\"ime set " + imeId + "\" for user #" + userId
- + " failed due to its unrecognized IME ID.");
- } else {
- out.print("Input method ");
- out.print(imeId);
- out.print(" selected for user #");
- out.println(userId);
- }
- hasFailed |= failedToSelectUnknownIme;
- }
- }
- }
- return hasFailed ? ShellCommandResult.FAILURE : ShellCommandResult.SUCCESS;
- }
-
- /**
- * Handles {@code adb shell ime reset-ime}.
- * @param shellCommand {@link ShellCommand} object that is handling this command.
- * @return Exit code of the command.
- */
- @BinderThread
- @ShellCommandResult
- private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) {
- final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand);
- synchronized (ImfLock.class) {
- try (PrintWriter out = shellCommand.getOutPrintWriter()) {
- final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
- for (int userId : userIds) {
- if (!userHasDebugPriv(userId, shellCommand)) {
- continue;
- }
- final String nextIme;
- final List<InputMethodInfo> nextEnabledImes;
- if (userId == mSettings.getCurrentUserId()) {
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
- 0 /* flags */, null /* resultReceiver */,
- SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
- mBindingController.unbindCurrentMethod();
-
- // Enable default IMEs, disable others
- ArrayList<InputMethodInfo> toDisable =
- mSettings.getEnabledInputMethodListLocked();
- ArrayList<InputMethodInfo> defaultEnabled =
- InputMethodInfoUtils.getDefaultEnabledImes(mContext, mMethodList);
- toDisable.removeAll(defaultEnabled);
- for (InputMethodInfo info : toDisable) {
- setInputMethodEnabledLocked(info.getId(), false);
- }
- for (InputMethodInfo info : defaultEnabled) {
- setInputMethodEnabledLocked(info.getId(), true);
- }
- // Choose new default IME, reset to none if no IME available.
- if (!chooseNewDefaultIMELocked()) {
- resetSelectedInputMethodAndSubtypeLocked(null);
- }
- updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
- InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, mSettings.getCurrentUserId()),
- mSettings.getEnabledInputMethodListLocked());
- nextIme = mSettings.getSelectedInputMethod();
- nextEnabledImes = mSettings.getEnabledInputMethodListLocked();
- } else {
- final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
- final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
- final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
- new ArrayMap<>();
- AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
- queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- methodMap, methodList, DirectBootAwareness.AUTO,
- mSettings.getEnabledInputMethodNames());
- final InputMethodSettings settings = new InputMethodSettings(mContext,
- methodMap, userId, false);
-
- nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
- methodList);
- nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
- nextEnabledImes).getId();
-
- // Reset enabled IMEs.
- settings.putEnabledInputMethodsStr("");
- nextEnabledImes.forEach(
- imi -> settings.appendAndPutEnabledInputMethodLocked(
- imi.getId(), false));
-
- // Reset selected IME.
- settings.putSelectedInputMethod(nextIme);
- settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
- }
- out.println("Reset current and enabled IMEs for user #" + userId);
- out.println(" Selected: " + nextIme);
- nextEnabledImes.forEach(ime -> out.println(" Enabled: " + ime.getId()));
- }
- }
- }
- return ShellCommandResult.SUCCESS;
- }
-
- /**
- * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
- * @param shellCommand {@link ShellCommand} object that is handling this command.
- * @return Exit code of the command.
- */
- @BinderThread
- @ShellCommandResult
- private int handleShellCommandTraceInputMethod(@NonNull ShellCommand shellCommand) {
- final String cmd = shellCommand.getNextArgRequired();
- try (PrintWriter pw = shellCommand.getOutPrintWriter()) {
- switch (cmd) {
- case "start":
- ImeTracing.getInstance().startTrace(pw);
- break; // proceed to the next step to update the IME client processes.
- case "stop":
- ImeTracing.getInstance().stopTrace(pw);
- break; // proceed to the next step to update the IME client processes.
- case "save-for-bugreport":
- ImeTracing.getInstance().saveForBugreport(pw);
- // no need to update the IME client processes.
- return ShellCommandResult.SUCCESS;
- default:
- pw.println("Unknown command: " + cmd);
- pw.println("Input method trace options:");
- pw.println(" start: Start tracing");
- pw.println(" stop: Stop tracing");
- // no need to update the IME client processes.
- return ShellCommandResult.FAILURE;
- }
- }
- boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
- ArrayMap<IBinder, ClientState> clients;
- synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
- }
- for (ClientState state : clients.values()) {
- if (state != null) {
- state.mClient.setImeTraceEnabled(isImeTraceEnabled);
- }
- }
- return ShellCommandResult.SUCCESS;
- }
-
- /**
- * @param userId the actual user handle obtained by {@link UserHandle#getIdentifier()}
- * and *not* pseudo ids like {@link UserHandle#USER_ALL etc}.
- * @return {@code true} if userId has debugging privileges.
- * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}.
- */
- private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) {
- if (mUserManagerInternal.hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, userId)) {
- shellCommand.getErrPrintWriter().println("User #" + userId
- + " is restricted with DISALLOW_DEBUGGING_FEATURES.");
- return false;
- }
- return true;
- }
-
- /** @hide */
- @Override
- public IImeTracker getImeTrackerService() {
- return mImeTrackerService;
- }
-
- /**
- * Creates an IME request tracking token for the current focused client.
- *
- * @param show whether this is a show or a hide request.
- * @param origin the origin of the IME request.
- * @param reason the reason why the IME request was created.
- */
- @NonNull
- private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
- @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
- final int uid = mCurFocusedWindowClient != null
- ? mCurFocusedWindowClient.mUid
- : -1;
- final String packageName = mCurFocusedWindowEditorInfo != null
- ? mCurFocusedWindowEditorInfo.packageName
- : "uid(" + uid + ")";
-
- if (show) {
- return ImeTracker.forLogging().onRequestShow(packageName, uid, origin, reason);
- } else {
- return ImeTracker.forLogging().onRequestHide(packageName, uid, origin, reason);
- }
- }
-
- private static final class InputMethodPrivilegedOperationsImpl
- extends IInputMethodPrivilegedOperations.Stub {
- private final CarInputMethodManagerService mImms;
- @NonNull
- private final IBinder mToken;
- InputMethodPrivilegedOperationsImpl(CarInputMethodManagerService imms,
- @NonNull IBinder token) {
- mImms = imms;
- mToken = token;
- }
-
- @BinderThread
- @Override
- public void setImeWindowStatusAsync(int vis, int backDisposition) {
- mImms.setImeWindowStatus(mToken, vis, backDisposition);
- }
-
- @BinderThread
- @Override
- public void reportStartInputAsync(IBinder startInputToken) {
- mImms.reportStartInput(mToken, startInputToken);
- }
-
- @BinderThread
- @Override
- public void createInputContentUriToken(Uri contentUri, String packageName,
- AndroidFuture future /* T=IBinder */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<IBinder> typedFuture = future;
- try {
- typedFuture.complete(mImms.createInputContentUriToken(
- mToken, contentUri, packageName).asBinder());
- } catch (Throwable e) {
- typedFuture.completeExceptionally(e);
- }
- }
-
- @BinderThread
- @Override
- public void reportFullscreenModeAsync(boolean fullscreen) {
- mImms.reportFullscreenMode(mToken, fullscreen);
- }
-
- @BinderThread
- @Override
- public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Void> typedFuture = future;
- try {
- mImms.setInputMethod(mToken, id);
- typedFuture.complete(null);
- } catch (Throwable e) {
- typedFuture.completeExceptionally(e);
- }
- }
-
- @BinderThread
- @Override
- public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype,
- AndroidFuture future /* T=Void */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Void> typedFuture = future;
- try {
- mImms.setInputMethodAndSubtype(mToken, id, subtype);
- typedFuture.complete(null);
- } catch (Throwable e) {
- typedFuture.completeExceptionally(e);
- }
- }
-
- @BinderThread
- @Override
- public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason,
- AndroidFuture future /* T=Void */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Void> typedFuture = future;
- try {
- mImms.hideMySoftInput(mToken, flags, reason);
- typedFuture.complete(null);
- } catch (Throwable e) {
- typedFuture.completeExceptionally(e);
- }
- }
-
- @BinderThread
- @Override
- public void showMySoftInput(int flags, AndroidFuture future /* T=Void */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Void> typedFuture = future;
- try {
- mImms.showMySoftInput(mToken, flags);
- typedFuture.complete(null);
- } catch (Throwable e) {
- typedFuture.completeExceptionally(e);
- }
- }
-
- @BinderThread
- @Override
- public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) {
- mImms.updateStatusIcon(mToken, packageName, iconId);
- }
-
- @BinderThread
- @Override
- public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Boolean> typedFuture = future;
- try {
- typedFuture.complete(mImms.switchToPreviousInputMethod(mToken));
- } catch (Throwable e) {
- typedFuture.completeExceptionally(e);
- }
- }
-
- @BinderThread
- @Override
- public void switchToNextInputMethod(boolean onlyCurrentIme,
- AndroidFuture future /* T=Boolean */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Boolean> typedFuture = future;
- try {
- typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme));
- } catch (Throwable e) {
- typedFuture.completeExceptionally(e);
- }
- }
-
- @BinderThread
- @Override
- public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Boolean> typedFuture = future;
- try {
- typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken));
- } catch (Throwable e) {
- typedFuture.completeExceptionally(e);
- }
- }
-
- @BinderThread
- @Override
- public void notifyUserActionAsync() {
- mImms.notifyUserAction(mToken);
- }
-
- @BinderThread
- @Override
- public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
- @Nullable ImeTracker.Token statsToken) {
- mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken);
- }
-
- @BinderThread
- @Override
- public void onStylusHandwritingReady(int requestId, int pid) {
- mImms.onStylusHandwritingReady(requestId, pid);
- }
-
- @BinderThread
- @Override
- public void resetStylusHandwriting(int requestId) {
- mImms.resetStylusHandwriting(requestId);
- }
- }
-}
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodMenuController.java b/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodMenuController.java
deleted file mode 100644
index a3bfd5e..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/CarInputMethodMenuController.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static com.android.server.inputmethod.CarInputMethodManagerService.DEBUG;
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
-
-import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
-import android.widget.ArrayAdapter;
-import android.widget.RadioButton;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
-import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-import com.android.server.wm.WindowManagerInternal;
-
-import java.util.List;
-
-/** A controller to show/hide the input method menu */
-final class CarInputMethodMenuController {
- private static final String TAG = CarInputMethodMenuController.class.getSimpleName();
-
- private final CarInputMethodManagerService mService;
- private final InputMethodUtils.InputMethodSettings mSettings;
- private final InputMethodSubtypeSwitchingController mSwitchingController;
- private final ArrayMap<String, InputMethodInfo> mMethodMap;
- private final WindowManagerInternal mWindowManagerInternal;
-
- private AlertDialog.Builder mDialogBuilder;
- private AlertDialog mSwitchingDialog;
- private View mSwitchingDialogTitleView;
- private InputMethodInfo[] mIms;
- private int[] mSubtypeIds;
-
- private boolean mShowImeWithHardKeyboard;
-
- @GuardedBy("ImfLock.class")
- @Nullable
- private InputMethodDialogWindowContext mDialogWindowContext;
-
- CarInputMethodMenuController(CarInputMethodManagerService service) {
- mService = service;
- mSettings = mService.mSettings;
- mSwitchingController = mService.mSwitchingController;
- mMethodMap = mService.mMethodMap;
- mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
- }
-
- void showInputMethodMenu(boolean showAuxSubtypes, int displayId) {
- if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
-
- final boolean isScreenLocked = isScreenLocked();
-
- final String lastInputMethodId = mSettings.getSelectedInputMethod();
- int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
- if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
-
- synchronized (ImfLock.class) {
- final List<ImeSubtypeListItem> imList = mSwitchingController
- .getSortedInputMethodAndSubtypeListForImeMenuLocked(
- showAuxSubtypes, isScreenLocked);
- if (imList.isEmpty()) {
- return;
- }
-
- hideInputMethodMenuLocked();
-
- if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
- final InputMethodSubtype currentSubtype =
- mService.getCurrentInputMethodSubtypeLocked();
- if (currentSubtype != null) {
- final String curMethodId = mService.getSelectedMethodIdLocked();
- final InputMethodInfo currentImi = mMethodMap.get(curMethodId);
- lastInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
- currentImi, currentSubtype.hashCode());
- }
- }
-
- final int size = imList.size();
- mIms = new InputMethodInfo[size];
- mSubtypeIds = new int[size];
- int checkedItem = 0;
- for (int i = 0; i < size; ++i) {
- final ImeSubtypeListItem item = imList.get(i);
- mIms[i] = item.mImi;
- mSubtypeIds[i] = item.mSubtypeId;
- if (mIms[i].getId().equals(lastInputMethodId)) {
- int subtypeId = mSubtypeIds[i];
- if ((subtypeId == NOT_A_SUBTYPE_ID)
- || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
- || (subtypeId == lastInputMethodSubtypeId)) {
- checkedItem = i;
- }
- }
- }
-
- if (mDialogWindowContext == null) {
- mDialogWindowContext = new InputMethodDialogWindowContext();
- }
- final Context dialogWindowContext = mDialogWindowContext.get(displayId);
- mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
- mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
-
- final Context dialogContext = mDialogBuilder.getContext();
- final TypedArray a = dialogContext.obtainStyledAttributes(null,
- com.android.internal.R.styleable.DialogPreference,
- com.android.internal.R.attr.alertDialogStyle, 0);
- final Drawable dialogIcon = a.getDrawable(
- com.android.internal.R.styleable.DialogPreference_dialogIcon);
- a.recycle();
-
- mDialogBuilder.setIcon(dialogIcon);
-
- final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
- final View tv = inflater.inflate(
- com.android.internal.R.layout.input_method_switch_dialog_title, null);
- mDialogBuilder.setCustomTitle(tv);
-
- // Setup layout for a toggle switch of the hardware keyboard
- mSwitchingDialogTitleView = tv;
- mSwitchingDialogTitleView
- .findViewById(com.android.internal.R.id.hard_keyboard_section)
- .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
- ? View.VISIBLE : View.GONE);
- final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
- com.android.internal.R.id.hard_keyboard_switch);
- hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
- hardKeySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
- mSettings.setShowImeWithHardKeyboard(isChecked);
- // Ensure that the input method dialog is dismissed when changing
- // the hardware keyboard state.
- hideInputMethodMenu();
- });
-
- final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
- com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
- final DialogInterface.OnClickListener choiceListener = (dialog, which) -> {
- synchronized (ImfLock.class) {
- if (mIms == null || mIms.length <= which || mSubtypeIds == null
- || mSubtypeIds.length <= which) {
- return;
- }
- final InputMethodInfo im = mIms[which];
- int subtypeId = mSubtypeIds[which];
- adapter.mCheckedItem = which;
- adapter.notifyDataSetChanged();
- if (im != null) {
- if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
- subtypeId = NOT_A_SUBTYPE_ID;
- }
- mService.setInputMethodLocked(im.getId(), subtypeId);
- }
- hideInputMethodMenuLocked();
- }
- };
- mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
-
- mSwitchingDialog = mDialogBuilder.create();
- mSwitchingDialog.setCanceledOnTouchOutside(true);
- final Window w = mSwitchingDialog.getWindow();
- final WindowManager.LayoutParams attrs = w.getAttributes();
- w.setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
- w.setHideOverlayWindows(true);
- // Use an alternate token for the dialog for that window manager can group the token
- // with other IME windows based on type vs. grouping based on whichever token happens
- // to get selected by the system later on.
- attrs.token = dialogWindowContext.getWindowContextToken();
- attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- attrs.setTitle("Select input method");
- w.setAttributes(attrs);
- mService.updateSystemUiLocked();
- mService.sendOnNavButtonFlagsChangedLocked();
- mSwitchingDialog.show();
- }
- }
-
- private boolean isScreenLocked() {
- return mWindowManagerInternal.isKeyguardLocked()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId());
- }
-
- void updateKeyboardFromSettingsLocked() {
- mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
- if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
- && mSwitchingDialog.isShowing()) {
- final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
- com.android.internal.R.id.hard_keyboard_switch);
- hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
- }
- }
-
- /**
- * Hides the input method switcher menu.
- */
- void hideInputMethodMenu() {
- synchronized (ImfLock.class) {
- hideInputMethodMenuLocked();
- }
- }
-
- /**
- * Hides the input method switcher menu.
- */
- @GuardedBy("ImfLock.class")
- void hideInputMethodMenuLocked() {
- if (DEBUG) Slog.v(TAG, "Hide switching menu");
-
- if (mSwitchingDialog != null) {
- mSwitchingDialog.dismiss();
- mSwitchingDialog = null;
- mSwitchingDialogTitleView = null;
-
- mService.updateSystemUiLocked();
- mService.sendOnNavButtonFlagsChangedLocked();
- mDialogBuilder = null;
- mIms = null;
- }
- }
-
- AlertDialog getSwitchingDialogLocked() {
- return mSwitchingDialog;
- }
-
- boolean getShowImeWithHardKeyboard() {
- return mShowImeWithHardKeyboard;
- }
-
- boolean isisInputMethodPickerShownForTestLocked() {
- if (mSwitchingDialog == null) {
- return false;
- }
- return mSwitchingDialog.isShowing();
- }
-
- void handleHardKeyboardStatusChange(boolean available) {
- if (DEBUG) {
- Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
- }
- synchronized (ImfLock.class) {
- if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
- && mSwitchingDialog.isShowing()) {
- mSwitchingDialogTitleView.findViewById(
- com.android.internal.R.id.hard_keyboard_section).setVisibility(
- available ? View.VISIBLE : View.GONE);
- }
- }
- }
-
- private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
- private final LayoutInflater mInflater;
- private final int mTextViewResourceId;
- private final List<ImeSubtypeListItem> mItemsList;
- public int mCheckedItem;
- private ImeSubtypeListAdapter(Context context, int textViewResourceId,
- List<ImeSubtypeListItem> itemsList, int checkedItem) {
- super(context, textViewResourceId, itemsList);
-
- mTextViewResourceId = textViewResourceId;
- mItemsList = itemsList;
- mCheckedItem = checkedItem;
- mInflater = LayoutInflater.from(context);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final View view = convertView != null ? convertView
- : mInflater.inflate(mTextViewResourceId, null);
- if (position < 0 || position >= mItemsList.size()) return view;
- final ImeSubtypeListItem item = mItemsList.get(position);
- final CharSequence imeName = item.mImeName;
- final CharSequence subtypeName = item.mSubtypeName;
- final TextView firstTextView = view.findViewById(android.R.id.text1);
- final TextView secondTextView = view.findViewById(android.R.id.text2);
- if (TextUtils.isEmpty(subtypeName)) {
- firstTextView.setText(imeName);
- secondTextView.setVisibility(View.GONE);
- } else {
- firstTextView.setText(subtypeName);
- secondTextView.setText(imeName);
- secondTextView.setVisibility(View.VISIBLE);
- }
- final RadioButton radioButton = view.findViewById(com.android.internal.R.id.radio);
- radioButton.setChecked(position == mCheckedItem);
- return view;
- }
- }
-}
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/InputMethodManagerServiceProxy.java b/builtInServices/src_imms/com/android/server/inputmethod/InputMethodManagerServiceProxy.java
deleted file mode 100644
index e7bb329..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/InputMethodManagerServiceProxy.java
+++ /dev/null
@@ -1,1133 +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.inputmethod;
-
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
-import static android.os.IServiceManager.DUMP_FLAG_PROTO;
-
-import android.annotation.BinderThread;
-import android.annotation.MainThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.annotation.WorkerThread;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
-import android.window.ImeOnBackInvokedDispatcher;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
-import com.android.internal.inputmethod.IImeTracker;
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
-import com.android.internal.inputmethod.IInputMethodClient;
-import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
-import com.android.internal.inputmethod.IRemoteInputConnection;
-import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
-import com.android.internal.inputmethod.InputBindResult;
-import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.view.IInputMethodManager;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.utils.Slogf;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-/**
- * Proxy used to host IMMSs per user and reroute requests to the user associated IMMS.
- *
- * @hide
- */
-public final class InputMethodManagerServiceProxy extends IInputMethodManager.Stub {
-
- private static final String IMMS_TAG = InputMethodManagerServiceProxy.class.getSimpleName();
- private static final boolean DBG = Log.isLoggable(IMMS_TAG, Log.DEBUG);
-
- // System property used to disable IMMS proxy.
- // When set to true, Android Core's original IMMS will be launched instead.
- // Note: this flag only takes effects on non user builds.
- public static final String DISABLE_MU_IMMS = "persist.fw.car.test.disable_mu_imms";
-
- private static final ExecutorService sExecutor = Executors.newCachedThreadPool();
-
- private final ReentrantReadWriteLock mRwLock = new ReentrantReadWriteLock();
-
- @GuardedBy("mRwLock")
- private final SparseArray<CarInputMethodManagerService> mServicesForUser = new SparseArray<>();
-
- @GuardedBy("mRwLock")
- private final SparseArray<InputMethodManagerInternal> mLocalServicesForUser =
- new SparseArray<>();
-
- private final Context mContext;
- private InputMethodManagerInternalProxy mInternalProxy;
-
- public InputMethodManagerServiceProxy(Context context) {
- mContext = context;
- mInternalProxy = new InputMethodManagerInternalProxy();
- }
-
- @UserIdInt
- private int getCallingUserId() {
- final int uid = Binder.getCallingUid();
- return UserHandle.getUserId(uid);
- }
-
- InputMethodManagerInternal getLocalServiceProxy() {
- return mInternalProxy;
- }
-
- CarInputMethodManagerService createAndRegisterServiceFor(@UserIdInt int userId) {
- Slogf.d(IMMS_TAG, "Starting IMMS and IMMI for user {%d}", userId);
- CarInputMethodManagerService imms;
- try {
- mRwLock.writeLock().lock();
- if ((imms = mServicesForUser.get(userId)) != null) {
- return imms;
- }
- imms = new CarInputMethodManagerService(mContext, sExecutor);
- mServicesForUser.set(userId, imms);
- InputMethodManagerInternal localService = imms.getInputMethodManagerInternal();
- mLocalServicesForUser.set(userId, localService);
- } finally {
- mRwLock.writeLock().unlock();
- }
- imms.systemRunning();
- Slogf.d(IMMS_TAG, "Started IMMS and IMMI for user {%d}", userId);
- return imms;
- }
-
- CarInputMethodManagerService getServiceForUser(@UserIdInt int userId) {
- try {
- mRwLock.readLock().lock();
- return mServicesForUser.get(userId);
- } finally {
- mRwLock.readLock().unlock();
- }
- }
-
- InputMethodManagerInternal getLocalServiceForUser(@UserIdInt int userId) {
- try {
- mRwLock.readLock().lock();
- return mLocalServicesForUser.get(userId);
- } finally {
- mRwLock.readLock().unlock();
- }
- }
-
- /**
- * SystemService for CarInputMethodManagerServices.
- *
- * If {@code fw.enable_imms_proxy} system property is set to {@code false}, then it just
- * delegate to Android Core original {@link InputMethodManagerService.Lifecycle}.
- *
- * TODO(b/245798405): make Lifecycle class easier to test and add tests for it
- */
- public static class Lifecycle extends SystemService {
- private static final String LIFECYCLE_TAG =
- IMMS_TAG + "." + Lifecycle.class.getSimpleName();
-
- private final InputMethodManagerServiceProxy mServiceProxy;
- private final Context mContext;
- private final UserManagerInternal mUserManagerInternal;
- private HandlerThread mWorkerThread;
- private Handler mHandler;
-
- // Android core IMMS to be used when IMMS Proxy is disabled
- private final InputMethodManagerService.Lifecycle mCoreImmsLifecycle;
-
- /**
- * Initializes the system service for InputMethodManagerServiceProxy.
- */
- public Lifecycle(@NonNull Context context) {
- super(context);
- mContext = context;
- mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
- mServiceProxy = new InputMethodManagerServiceProxy(mContext);
- if (!Build.IS_USER && SystemProperties.getBoolean(
- DISABLE_MU_IMMS, /* defaultValue= */ false)) {
- mCoreImmsLifecycle = new InputMethodManagerService.Lifecycle(mContext);
- } else {
- mCoreImmsLifecycle = null;
- }
- }
-
- private boolean isImmsProxyEnabled() {
- return mCoreImmsLifecycle == null;
- }
-
- @MainThread
- @Override
- public void onStart() {
- if (DBG) {
- Slogf.d(LIFECYCLE_TAG, "Entering #onStart (IMMS Proxy enabled={%s})",
- isImmsProxyEnabled());
- }
- if (!isImmsProxyEnabled()) {
- mCoreImmsLifecycle.onStart();
- return;
- }
- mWorkerThread = new HandlerThread(IMMS_TAG);
- mWorkerThread.start();
- mHandler = new Handler(mWorkerThread.getLooper(), msg -> false, true);
-
- // Register broadcast receivers for user state changes
- IntentFilter broadcastFilterForSystemUser = new IntentFilter();
- broadcastFilterForSystemUser.addAction(Intent.ACTION_LOCALE_CHANGED);
- mContext.registerReceiver(new ImmsBroadcastReceiverForSystemUser(),
- broadcastFilterForSystemUser);
-
- // Register binders
- LocalServices.addService(InputMethodManagerInternal.class,
- mServiceProxy.getLocalServiceProxy());
- publishBinderService(Context.INPUT_METHOD_SERVICE, mServiceProxy,
- false /*allowIsolated*/,
- DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
- }
-
- @MainThread
- @Override
- public void onBootPhase(int phase) {
- if (DBG) {
- Slogf.d(LIFECYCLE_TAG,
- "Entering #onBootPhase with phase={%d} (IMMS Proxy enabled={%s})", phase,
- isImmsProxyEnabled());
- }
- if (!isImmsProxyEnabled()) {
- mCoreImmsLifecycle.onBootPhase(phase);
- return;
- }
- if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- Lifecycle::onBootPhaseReceived, this, phase));
- }
- }
-
- @WorkerThread
- private void onBootPhaseReceived(int phase) {
- if (DBG) {
- Slogf.d(LIFECYCLE_TAG, "Entering #onBootPhaseReceived with phase={%d}", phase);
- }
- int[] userIds = mUserManagerInternal.getUserIds();
- for (int i = 0; i < userIds.length; ++i) {
- mServiceProxy.createAndRegisterServiceFor(userIds[i]);
- }
- }
-
- @MainThread
- @Override
- public void onUserStarting(@NonNull TargetUser user) {
- if (DBG) {
- Slogf.d(LIFECYCLE_TAG,
- "Entering #onUserStarting with user={%s} (IMMS Proxy enabled={%s})", user,
- isImmsProxyEnabled());
- }
- if (!isImmsProxyEnabled()) {
- mCoreImmsLifecycle.onUserStarting(user);
- return;
- }
- mHandler.sendMessage(PooledLambda.obtainMessage(
- Lifecycle::onUserStartingReceived, this, user));
- }
-
- @WorkerThread
- private void onUserStartingReceived(@NonNull TargetUser user) {
- // This method may be invoked under WindowManagerGlobalLock, therefore the code must be
- // run on separated thread to avoid deadlock (imms#systemRUnning and
- // imms#scheduleSwitchUserTaskLocked will try to acquire WindowManagerGlobalLock).
- sExecutor.execute(() -> {
- CarInputMethodManagerService imms = mServiceProxy.createAndRegisterServiceFor(
- user.getUserIdentifier());
- synchronized (ImfLock.class) {
- imms.scheduleSwitchUserTaskLocked(user.getUserIdentifier(),
- /* clientToBeReset= */ null);
- }
- });
- }
-
- @MainThread
- @Override
- public void onUserUnlocking(@NonNull TargetUser user) {
- if (DBG) {
- Slogf.d(LIFECYCLE_TAG,
- "Entering #onUserUnlockingReceived with to={%s} (IMMS Proxy enabled={%s})",
- user, isImmsProxyEnabled());
- }
- if (!isImmsProxyEnabled()) {
- mCoreImmsLifecycle.onUserUnlocking(user);
- return;
- }
- mHandler.sendMessage(PooledLambda.obtainMessage(
- Lifecycle::onUserUnlockingReceived, this, user));
- }
-
- @WorkerThread
- private void onUserUnlockingReceived(@NonNull TargetUser user) {
- CarInputMethodManagerService service = mServiceProxy.getServiceForUser(
- user.getUserIdentifier());
- if (service != null) {
- // Called on ActivityManager thread.
- service.notifySystemUnlockUser(user.getUserIdentifier());
- }
- }
-
- @MainThread
- @Override
- public void onUserStopping(@NonNull TargetUser user) {
- if (DBG) {
- Slogf.d(LIFECYCLE_TAG,
- "Entering #onUserStoppingReceived with userId={%d} (IMMS Proxy "
- + "enabled={%s})",
- user.getUserIdentifier(), isImmsProxyEnabled());
- }
- if (!isImmsProxyEnabled()) {
- mCoreImmsLifecycle.onUserStopping(user);
- return;
- }
- sExecutor.execute(
- () -> mServiceProxy.removeCarInputMethodManagerServiceForUser(
- user.getUserIdentifier()));
- }
-
- @MainThread
- @Override
- public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
- if (DBG) {
- Slogf.d(LIFECYCLE_TAG,
- "Entering #onUserSwitching with from={%d} and to={%d} (IMMS Proxy "
- + "enabled={%s})",
- from.getUserIdentifier(), to.getUserIdentifier(), isImmsProxyEnabled());
- }
- if (!isImmsProxyEnabled()) {
- mCoreImmsLifecycle.onUserSwitching(from, to);
- }
- }
-
- /**
- * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to the system
- * user only.
- */
- private final class ImmsBroadcastReceiverForSystemUser extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
- mServiceProxy.onActionLocaleChanged();
- } else {
- Slogf.w(LIFECYCLE_TAG, "Unexpected intent " + intent);
- }
- }
- }
- }
-
- private void removeCarInputMethodManagerServiceForUser(int userId) {
- mRwLock.writeLock().lock();
- try {
- CarInputMethodManagerService imms = mServicesForUser.get(userId);
- if (imms == null) {
- return;
- }
- mServicesForUser.remove(userId);
- mLocalServicesForUser.remove(userId);
- imms.systemShutdown();
- } finally {
- mRwLock.writeLock().unlock();
- }
- Slogf.i(IMMS_TAG, "Removed CarIMMS for user {%d}", userId);
- }
-
- /**
- * Dump this IMMS Proxy object. If `--user` arg is pass (along with an existing user id) then
- * it will just dump the user associated IMMS.
- */
- @BinderThread
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, IMMS_TAG, pw)) {
- Slogf.w(IMMS_TAG, "Permission denied for #dump");
- return;
- }
-
- // Check if --user is set. If set, then just dump the user's IMMS.
- int userIdArg = parseUserArgIfPresent(args);
- if (userIdArg != UserHandle.USER_NULL) {
- mServicesForUser.get(userIdArg).dump(fd, pw, args);
- return;
- }
- pw.println("*InputMethodManagerServiceProxy");
- pw.println("**mServicesForUser**");
- try {
- mRwLock.readLock().lock();
- if (parseBriefArg(args)) {
- // Dump brief
- for (int i = 0; i < mServicesForUser.size(); i++) {
- int userId = mServicesForUser.keyAt(i);
- CarInputMethodManagerService imms = mServicesForUser.valueAt(i);
- pw.println(" userId=" + userId + " imms=" + imms.hashCode() + " {autofill="
- + imms.getAutofillController() + "}");
- }
- pw.println("**mLocalServicesForUser**");
- for (int i = 0; i < mLocalServicesForUser.size(); i++) {
- int userId = mLocalServicesForUser.keyAt(i);
- InputMethodManagerInternal immi = mLocalServicesForUser.valueAt(i);
- pw.println(" userId=" + userId + " immi=" + immi.hashCode());
- }
- } else {
- // Dump full
- for (int i = 0; i < mServicesForUser.size(); i++) {
- int userId = mServicesForUser.keyAt(i);
- pw.println("**CarInputMethodManagerService for userId=" + userId);
- CarInputMethodManagerService imms = mServicesForUser.valueAt(i);
- imms.dump(fd, pw, args);
- }
- }
- } finally {
- mRwLock.readLock().unlock();
- }
- pw.flush();
- }
-
- private boolean parseBriefArg(String[] args) {
- if (args == null) {
- return false;
- }
- for (int i = 0; i < args.length; i++) {
- if (args[i].equals("--brief")) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Parse the args string and returns the value of `--user` argument. Returns
- * {@link UserHandle.USER_NULL} in case of `--user` is not in args.
- *
- * @return the value of `--user` argument or UserHandle.USER_NULL if `--user` is not in args
- * @throws IllegalArgumentException if `--user` arg is not passed along a user id
- * @throws NumberFormatException if the value passed along `--user` is not an integer
- */
- private int parseUserArgIfPresent(String[] args) {
- if (args == null) {
- return UserHandle.USER_NULL;
- }
- for (int i = 0; i < args.length; ++i) {
- if ("--user".equals(args[i])) {
- if (i == args.length - 1) {
- throw new IllegalArgumentException("User id must be passed within --user arg");
- }
- try {
- return Integer.parseInt(args[++i]);
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException(
- "Expected an integer value for `--user` arg, got " + args[i]);
- }
- }
- }
- return UserHandle.USER_NULL;
- }
-
- private void onActionLocaleChanged() {
- try {
- mRwLock.readLock().lock();
- for (int i = 0; i < mServicesForUser.size(); i++) {
- int userId = mServicesForUser.keyAt(i);
- Slogf.i(IMMS_TAG, "Updating location for user {%d} Car IMMS", userId);
- CarInputMethodManagerService imms = mServicesForUser.valueAt(i);
- imms.onActionLocaleChanged();
- }
- } finally {
- mRwLock.readLock().unlock();
- }
- }
-
- // Delegate methods ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void addClient(IInputMethodClient client, IRemoteInputConnection inputmethod,
- int untrustedDisplayId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking addClient with untrustedDisplayId={%d}",
- callingUserId, untrustedDisplayId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.addClient(client, inputmethod, untrustedDisplayId);
- }
-
- @Override
- public List<InputMethodInfo> getInputMethodList(int userId, int directBootAwareness) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking getInputMethodList with userId={%d}",
- callingUserId, userId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.getInputMethodList(userId, directBootAwareness);
- }
-
- @Override
- public List<InputMethodInfo> getEnabledInputMethodList(int userId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking getInputMethodList with userId={%d}",
- callingUserId, userId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.getEnabledInputMethodList(userId);
- }
-
- @Override
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
- boolean allowsImplicitlyEnabledSubtypes, int userId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG,
- "User {%d} invoking getEnabledInputMethodSubtypeList with imiId={%s}",
- callingUserId, imiId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
- userId);
- }
-
- @Override
- public InputMethodSubtype getLastInputMethodSubtype(int userId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking getLastInputMethodSubtype with userId={%d}",
- callingUserId, userId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.getLastInputMethodSubtype(userId);
- }
-
- @Override
- public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
- ImeTracker.Token statsToken, int flags, int lastClickToolType,
- ResultReceiver resultReceiver, int reason) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking showSoftInput with "
- + "windowToken={%s} and reason={%d}", callingUserId, windowToken, reason);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
- resultReceiver,
- reason);
- }
-
- @Override
- public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
- @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking hideSoftInput with "
- + "windowToken={%s} and reason={%d}", callingUserId, windowToken, reason);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
- reason);
- }
-
- @Override
- public InputBindResult startInputOrWindowGainedFocus(int startInputReason,
- IInputMethodClient client, IBinder windowToken, int startInputFlags,
- int softInputMode,
- int windowFlags, EditorInfo editorInfo, IRemoteInputConnection inputConnection,
- IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion, int userId,
- ImeOnBackInvokedDispatcher imeDispatcher) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking startInputOrWindowGainedFocus with "
- + "windowToken={%s} and reason={%d}", callingUserId, windowToken, userId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- InputBindResult result = imms.startInputOrWindowGainedFocus(startInputReason,
- client, windowToken, startInputFlags, softInputMode,
- windowFlags, editorInfo, inputConnection,
- remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
- imeDispatcher);
- if (DBG) {
- Slogf.d(IMMS_TAG, "Returning {%s} for startInputOrWindowGainedFocus / user {%d}",
- result,
- userId);
- }
- return result;
- }
-
- @Override
- public void showInputMethodPickerFromClient(IInputMethodClient client,
- int auxiliarySubtypeMode) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking showInputMethodPickerFromClient",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
- }
-
- @Override
- public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking getLastInputMethodSubtype with "
- + " auxiliarySubtypeMode={%d}, and displayId={%d}", callingUserId,
- auxiliarySubtypeMode, displayId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
- }
-
- @Override
- public boolean isInputMethodPickerShownForTest() {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking isInputMethodPickerShownForTest",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.isInputMethodPickerShownForTest();
- }
-
- @Override
- public InputMethodSubtype getCurrentInputMethodSubtype(int userId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG,
- "User {%d} invoking getCurrentInputMethodSubtype with userId={%d}",
- callingUserId, userId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.getCurrentInputMethodSubtype(userId);
- }
-
- @Override
- public void setAdditionalInputMethodSubtypes(String id, InputMethodSubtype[] subtypes,
- int userId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking setAdditionalInputMethodSubtypes with "
- + "id={%d} and userId={%d}", callingUserId, id, userId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.setAdditionalInputMethodSubtypes(id, subtypes, userId);
- }
-
- @Override
- public void setExplicitlyEnabledInputMethodSubtypes(String imeId, int[] subtypeHashCodes,
- int userId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking setExplicitlyEnabledInputMethodSubtypes with "
- + "imeId={%d} and userId={%d}", callingUserId, imeId, userId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
- }
-
- @Override
- public int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking getInputMethodWindowVisibleHeight",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.getInputMethodWindowVisibleHeight(client);
- }
-
- @Override
- public void reportVirtualDisplayGeometryAsync(IInputMethodClient parentClient,
- int childDisplayId, float[] matrixValues) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking reportVirtualDisplayGeometryAsync",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.reportVirtualDisplayGeometryAsync(parentClient, childDisplayId, matrixValues);
- }
-
- @Override
- public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking reportPerceptibleAsync",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.reportPerceptibleAsync(windowToken, perceptible);
- }
-
- @Override
- public void removeImeSurface() {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking removeImeSurface",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.removeImeSurface();
- }
-
- @Override
- public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking removeImeSurfaceFromWindowAsync "
- + "with windowToken={%s}", callingUserId, windowToken);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.removeImeSurfaceFromWindowAsync(windowToken);
- }
-
- @Override
- public void startProtoDump(byte[] protoDump, int source, String where) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking startProtoDump", callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.startProtoDump(protoDump, source, where);
- }
-
- @Override
- public boolean isImeTraceEnabled() {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking isImeTraceEnabled", callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.isImeTraceEnabled();
- }
-
- @Override
- public void startImeTrace() {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking startImeTrace", callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.startImeTrace();
- }
-
- @Override
- public void stopImeTrace() {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking stopImeTrace", callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.stopImeTrace();
- }
-
- @Override
- public void startStylusHandwriting(IInputMethodClient client) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking startStylusHandwriting", callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.startStylusHandwriting(client);
- }
-
- @Override
- public void prepareStylusHandwritingDelegation(
- @NonNull IInputMethodClient client,
- @UserIdInt int userId,
- @NonNull String delegatePackageName,
- @NonNull String delegatorPackageName) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking prepareStylusHandwritingDelegation with"
- + "client={%s}, userId={%d}, delegatePackageName={%s}, "
- + "delegatorPackageName={%s}",
- callingUserId, client, delegatePackageName, delegatorPackageName);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.prepareStylusHandwritingDelegation(client, userId, delegatePackageName,
- delegatorPackageName);
- }
-
- @Override
- public boolean acceptStylusHandwritingDelegation(
- @NonNull IInputMethodClient client,
- @UserIdInt int userId,
- @NonNull String delegatePackageName,
- @NonNull String delegatorPackageName) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking acceptStylusHandwritingDelegation with"
- + "client={%s}, userId={%d}, delegatePackageName={%s}, "
- + "delegatorPackageName={%s}",
- callingUserId, client, delegatePackageName, delegatorPackageName);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.acceptStylusHandwritingDelegation(client, userId, delegatePackageName,
- delegatorPackageName);
- }
-
- @Override
- public boolean isStylusHandwritingAvailableAsUser(int userId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking isStylusHandwritingAvailableAsUser",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.isStylusHandwritingAvailableAsUser(userId);
- }
-
- @Override
- public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking addVirtualStylusIdForTestSession",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.addVirtualStylusIdForTestSession(client);
- }
-
- @Override
- public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking setStylusWindowIdleTimeoutForTest",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- imms.setStylusWindowIdleTimeoutForTest(client, timeout);
- }
-
- @Override
- public IImeTracker getImeTrackerService() {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking getImeTrackerService",
- callingUserId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.getImeTrackerService();
- }
-
- @Override
- public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
- final int callingUserId = getCallingUserId();
- if (DBG) {
- Slogf.d(IMMS_TAG, "User {%d} invoking getCurrentInputMethodInfoAsUser with userId={%d}",
- callingUserId, userId);
- }
- CarInputMethodManagerService imms = getServiceForUser(callingUserId);
- return imms.getCurrentInputMethodInfoAsUser(userId);
- }
-
- @BinderThread
- @Override
- public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
- @Nullable FileDescriptor err,
- @NonNull String[] args, @Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
- checkCallerIsRootOrShell(args, resultReceiver);
- int userId;
- try {
- userId = parseUserArgIfPresent(args);
- } catch (IllegalArgumentException | SecurityException e) {
- resultReceiver.send(-1 /* FAILURE */, null);
- Slogf.e(IMMS_TAG, "Failed parsing incoming shell command", e);
- return;
- }
- if (userId == UserHandle.USER_NULL) {
- Slogf.w(IMMS_TAG, "Ignoring incoming shell command {%s}, "
- + "no user was specified (use --user flag to specify the user id)",
- Arrays.toString(args));
- resultReceiver.send(-1 /* FAILURE */, null);
- return;
- }
- CarInputMethodManagerService imms = getServiceForUser(userId);
- if (imms == null) {
- Slogf.e(IMMS_TAG, String.format("Ignoring incoming shell command {%s},"
- + " there is no Car IMMS for user {%d}", Arrays.toString(args), userId));
- resultReceiver.send(-1 /* FAILURE */, null);
- return;
- }
- if (DBG) {
- Slogf.d(IMMS_TAG, "Running shell command {%s} on imms {%d}", Arrays.toString(args),
- userId);
- }
- imms.onShellCommand(in, out, err, args, callback, resultReceiver);
- resultReceiver.send(0 /* SUCCESS */, null);
- }
-
- private void checkCallerIsRootOrShell(String[] args, @NonNull ResultReceiver resultReceiver)
- throws SecurityException {
- final int callingUid = Binder.getCallingUid();
- // Regular adb shell will come with process SHELL_UID and adb root shell with ROOT_UID
- if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
- resultReceiver.send(-1 /* FAILURE */, null);
- String errorMsg = String.format("InputMethodManagerServiceProxy does not support"
- + " shell commands from non-shell users. callingUid={%d} args={%s}",
- callingUid, Arrays.toString(args));
- if (Process.isCoreUid(callingUid)) {
- // Let's not crash the calling process if the caller is one of core components
- // (this is the same logic adopted by Android Core's IMMS).
- Slogf.e(IMMS_TAG, errorMsg);
- return;
- }
- throw new SecurityException(errorMsg);
- }
- }
-
- class InputMethodManagerInternalProxy extends InputMethodManagerInternal {
- private final String mImmiTag =
- IMMS_TAG + "." + InputMethodManagerInternalProxy.class.getSimpleName();
-
- @Override
- public void setInteractive(boolean interactive) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking setInteractive", callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.setInteractive(interactive);
- }
-
- @Override
- public void hideCurrentInputMethod(int reason) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking hideCurrentInputMethod", callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.hideCurrentInputMethod(reason);
- }
-
- @Override
- public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking getInputMethodListAsUser=%d",
- callingUserId,
- userId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- return immi.getInputMethodListAsUser(userId);
- }
-
- @Override
- public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking getEnabledInputMethodListAsUser=%d",
- callingUserId, userId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- return immi.getEnabledInputMethodListAsUser(userId);
- }
-
- @Override
- public void onCreateInlineSuggestionsRequest(int userId,
- InlineSuggestionsRequestInfo requestInfo,
- IInlineSuggestionsRequestCallback cb) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking onCreateInlineSuggestionsRequest=%d",
- callingUserId, userId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.onCreateInlineSuggestionsRequest(userId, requestInfo, cb);
- }
-
- @Override
- public boolean switchToInputMethod(String imeId, int userId) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking switchToInputMethod=%d", callingUserId,
- userId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- return immi.switchToInputMethod(imeId, userId);
- }
-
- @Override
- public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking setInputMethodEnabled", callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- return immi.setInputMethodEnabled(imeId, enabled, userId);
- }
-
- @Override
- public void registerInputMethodListListener(InputMethodListListener listener) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking registerInputMethodListListener",
- callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.registerInputMethodListListener(listener);
- }
-
- @Override
- public boolean transferTouchFocusToImeWindow(
- @NonNull IBinder sourceInputToken, int displayId) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking transferTouchFocusToImeWindow",
- callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- return immi.transferTouchFocusToImeWindow(sourceInputToken, displayId);
- }
-
- @Override
- public void reportImeControl(@Nullable IBinder windowToken) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking reportImeControl", callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.reportImeControl(windowToken);
- }
-
- @Override
- public void onImeParentChanged() {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking onImeParentChanged", callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.onImeParentChanged();
- }
-
- @Override
- public void removeImeSurface() {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking removeImeSurface", callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.removeImeSurface();
- }
-
- @Override
- public void updateImeWindowStatus(boolean disableImeIcon) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking updateImeWindowStatus", callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.updateImeWindowStatus(disableImeIcon);
- }
-
- @Override
- public void maybeFinishStylusHandwriting() {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking maybeFinishStylusHandwriting",
- callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.maybeFinishStylusHandwriting();
- }
-
- @Override
- public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
- IAccessibilityInputMethodSession session) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking onSessionForAccessibilityCreated",
- callingUserId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.onSessionForAccessibilityCreated(accessibilityConnectionId, session);
- }
-
- @Override
- public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking unbindAccessibilityFromCurrentClient("
- + "accessibilityConnectionId=%d)", callingUserId,
- accessibilityConnectionId);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.unbindAccessibilityFromCurrentClient(accessibilityConnectionId);
- }
-
- @Override
- public void switchKeyboardLayout(int direction) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- if (DBG) {
- Slogf.d(mImmiTag, "User {%d} invoking switchKeyboardLayout(direction=%d)",
- callingUserId, direction);
- }
- InputMethodManagerInternal immi = getLocalServiceForUser(callingUserId);
- immi.switchKeyboardLayout(direction);
- }
- }
-}
diff --git a/builtInServices/src_imms/com/android/server/inputmethod/NullAutofillSuggestionsController.java b/builtInServices/src_imms/com/android/server/inputmethod/NullAutofillSuggestionsController.java
deleted file mode 100644
index 87314c0..0000000
--- a/builtInServices/src_imms/com/android/server/inputmethod/NullAutofillSuggestionsController.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.inputmethod;
-
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
-import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
-
-/**
- * A null implementation of {@link AutofillController}.
- */
-public class NullAutofillSuggestionsController implements AutofillController {
-
- @Override
- public void onCreateInlineSuggestionsRequest(int userId,
- InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback,
- boolean touchExplorationEnabled) {
- // Do nothing.
- }
-
- @Override
- public void performOnCreateInlineSuggestionsRequest() {
- // Do nothing.
- }
-
- @Override
- public void invalidateAutofillSession() {
- // Do nothing.
- }
-}
diff --git a/builtInServices/tests/Android.bp b/builtInServices/tests/Android.bp
index d537aa5..a2f596b 100644
--- a/builtInServices/tests/Android.bp
+++ b/builtInServices/tests/Android.bp
@@ -33,6 +33,7 @@ android_test {
"android.car.watchdoglib",
"androidx.test.ext.junit",
"androidx.test.rules",
+ "androidx.test.uiautomator_uiautomator",
"mockito-target-extended-minus-junit4",
"services.core",
"testng",
diff --git a/builtInServices/tests/res/raw/CSHS_classes.txt b/builtInServices/tests/res/raw/CSHS_classes.txt
new file mode 100644
index 0000000..ef75368
--- /dev/null
+++ b/builtInServices/tests/res/raw/CSHS_classes.txt
@@ -0,0 +1,19 @@
+android.content.res.CompatScaleWrapper
+com.android.internal.car.CarServiceHelperInterface
+com.android.internal.car.CarServiceHelperServiceUpdatable
+com.android.server.wm.ActivityInterceptResultWrapper
+com.android.server.wm.ActivityInterceptorInfoWrapper
+com.android.server.wm.ActivityOptionsWrapper
+com.android.server.wm.ActivityRecordWrapper
+com.android.server.wm.CalculateParams
+com.android.server.wm.CarActivityInterceptorInterface
+com.android.server.wm.CarActivityInterceptorUpdatable
+com.android.server.wm.CarDisplayCompatScaleProviderInterface
+com.android.server.wm.CarDisplayCompatScaleProviderUpdatable
+com.android.server.wm.CarLaunchParamsModifierInterface
+com.android.server.wm.CarLaunchParamsModifierUpdatable
+com.android.server.wm.LaunchParamsWrapper
+com.android.server.wm.RequestWrapper
+com.android.server.wm.TaskDisplayAreaWrapper
+com.android.server.wm.TaskWrapper
+com.android.server.wm.WindowLayoutWrapper
diff --git a/builtInServices/tests/src/com/android/car/rotary/ActivityResolverTest.java b/builtInServices/tests/src/com/android/car/rotary/ActivityResolverTest.java
new file mode 100644
index 0000000..de382d9
--- /dev/null
+++ b/builtInServices/tests/src/com/android/car/rotary/ActivityResolverTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.car.rotary;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.Condition;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * This test opens KitchenSink and verifies that ActivityResolver is supported by rotary controller.
+ * The test injects TAB KeyEvents to simulate rotary rotation events because the way RotaryService
+ * handles rotary rotation events is similar to the way Android framework handles TAB KeyEvent.
+ */
+public final class ActivityResolverTest {
+
+ private static final long WAIT_TIMEOUT_MS = 3_000;
+ private static final String TRIGGER_ACTIVITY_RESOLVER_RESOURCE_ID =
+ "com.google.android.car.kitchensink:id/trigger_activity_resolver";
+ private static final String DISMISS_BUTTON_RESOURCE_ID =
+ "com.google.android.car.kitchensink:id/dismiss_button";
+
+ private static final String KITCHEN_SINK_APP = "com.google.android.car.kitchensink";
+
+ private Instrumentation mInstrumentation;
+ private UiDevice mDevice;
+
+ @Before
+ public void setUp() throws IOException {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mDevice = UiDevice.getInstance(mInstrumentation);
+ closeKitchenSink();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ closeKitchenSink();
+ }
+
+ private void closeKitchenSink() throws IOException {
+ mDevice.executeShellCommand(String.format("am force-stop %s", KITCHEN_SINK_APP));
+ }
+
+ @Test
+ public void testListItemFocusable_threeItems() throws UiObjectNotFoundException, IOException {
+ launchResolverActivity();
+ assumeTrue(hasThreeListItems());
+
+ // When the ListView is focusable, it'll be focused after pressing TAB key. In this case,
+ // press the TAB key again to get the first list item focused.
+ UiObject list = mDevice.findObject(
+ new UiSelector().className(android.widget.ListView.class));
+ if (list.isFocusable()) {
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ }
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject listItem1 = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focusable(true).instance(0));
+ waitAndAssertFocused(listItem1);
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject listItem2 = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focusable(true).instance(1));
+ waitAndAssertFocused(listItem2);
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject listItem3 = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focusable(true).instance(2));
+ waitAndAssertFocused(listItem3);
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ // The focus shouldn't move since it has reached the last item.
+ waitAndAssertFocused(listItem3);
+ }
+
+ @Test
+ public void testListItemFocusable_twoItems() throws UiObjectNotFoundException, IOException {
+ launchResolverActivity();
+ assumeTrue(!hasThreeListItems());
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ // When the ListView is focusable, it'll be focused after pressing TAB key. In this case,
+ // press the TAB key again to get the "Just once" button focused.
+ UiObject list = mDevice.findObject(
+ new UiSelector().className(android.widget.ListView.class));
+ if (list.isFocusable()) {
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ }
+ UiObject justOnceButton = mDevice.findObject(new UiSelector()
+ .className(android.widget.Button.class).focusable(true).enabled(true).instance(0));
+ waitAndAssertFocused(justOnceButton);
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject alwaysButton = mDevice.findObject(new UiSelector()
+ .className(android.widget.Button.class).focusable(true).enabled(true).instance(1));
+ waitAndAssertFocused(alwaysButton);
+
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject listItem1 = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focusable(true).instance(0));
+ waitAndAssertFocused(listItem1);
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject listItem2 = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focusable(true).instance(1));
+ waitAndAssertFocused(listItem2);
+ }
+
+ @Test
+ public void testActionButtonsNotFocusable_threeItems()
+ throws UiObjectNotFoundException, IOException {
+ launchResolverActivity();
+ assumeTrue(hasThreeListItems());
+
+ // The two buttons should be disabled if the test activity is never opened by
+ // ActivityResolver.
+ UiObject justOnceButton = mDevice.findObject(new UiSelector()
+ .className(android.widget.Button.class).focusable(true).enabled(false).instance(0));
+ assertWithMessage("Failed to find the disabled justOnceButton")
+ .that(justOnceButton.exists())
+ .isTrue();
+
+ UiObject alwaysButton = mDevice.findObject(new UiSelector()
+ .className(android.widget.Button.class).focusable(true).enabled(false).instance(1));
+ assertWithMessage("Failed to find the disabled alwaysButton")
+ .that(alwaysButton.exists())
+ .isTrue();
+ }
+
+ @Test
+ public void testClickListItem_threeItems() throws UiObjectNotFoundException, IOException {
+ launchResolverActivity();
+ assumeTrue(hasThreeListItems());
+
+ // Press twice to make sure a list item gets focused.
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject listItem = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focused(true));
+ waitAndAssertFocused(listItem);
+
+ // Simulate rotary click on the focused listItem.
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
+
+ // After rotary click, the list will be re-layouted, and the listItem will lose focus.
+ // As a result, Android framework will focus on the first focusable & enabled view in app
+ // window, which is the justOnceButton.
+ UiObject justOnceButton = mDevice.findObject(new UiSelector()
+ .className(android.widget.Button.class).focusable(true).enabled(true).instance(0));
+ waitAndAssertFocused(justOnceButton);
+
+ // Simulate rotary clockwise rotation.
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject alwaysButton = mDevice.findObject(new UiSelector()
+ .className(android.widget.Button.class).focusable(true).enabled(true).instance(1));
+ waitAndAssertFocused(alwaysButton);
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ UiObject listItem1 = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focusable(true).instance(0));
+ waitAndAssertFocused(listItem1);
+
+ // Simulate rotary click on the listItem.
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
+
+ UiObject dismissButton =
+ mDevice.findObject(new UiSelector().resourceId(DISMISS_BUTTON_RESOURCE_ID));
+ waitAndAssertFocused(dismissButton);
+ }
+
+ @Test
+ public void testClickListItem_twoItems() throws UiObjectNotFoundException, IOException {
+ launchResolverActivity();
+ assumeTrue(!hasThreeListItems());
+
+
+ // When the ListView is focusable, it needs 4 rotations to focus on the list item.
+ // Otherwise, it needs 3 rotations.
+ UiObject list = mDevice.findObject(
+ new UiSelector().className(android.widget.ListView.class));
+ if (list.isFocusable()) {
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ }
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+
+ UiObject listItem = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focused(true));
+ waitAndAssertFocused(listItem);
+
+ // Simulate rotary click on the listItem.
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
+
+ UiObject dismissButton =
+ mDevice.findObject(new UiSelector().resourceId(DISMISS_BUTTON_RESOURCE_ID));
+ waitAndAssertFocused(dismissButton);
+ }
+
+ @Test
+ public void testClickJustOnceButton_twoItems() throws UiObjectNotFoundException, IOException {
+ launchResolverActivity();
+ assumeTrue(!hasThreeListItems());
+
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ // When the ListView is focusable, it'll be focused after pressing TAB key. In this case,
+ // press the TAB key again to get the "Just once" button focused.
+ UiObject list = mDevice.findObject(
+ new UiSelector().className(android.widget.ListView.class));
+ if (list.isFocusable()) {
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_TAB);
+ }
+ UiObject justOnceButton = mDevice.findObject(new UiSelector()
+ .className(android.widget.Button.class).focusable(true).enabled(true).instance(0));
+ waitAndAssertFocused(justOnceButton);
+
+ // Simulate rotary click on the justOnceButton.
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
+
+ UiObject dismissButton =
+ mDevice.findObject(new UiSelector().resourceId(DISMISS_BUTTON_RESOURCE_ID));
+ waitAndAssertFocused(dismissButton);
+ }
+
+ private boolean hasThreeListItems() {
+ // If a test activity has been launched by ActivityResolver before, the "Just once" button
+ // and "Always" button will be enabled, and the list will only show two items.
+ // Otherwise, the two buttons will be disabled and the list will show all three items.
+ // So the test will be different depending on whether it is the first time to run or not.
+ UiObject listItem3 = mDevice.findObject(new UiSelector()
+ .className(android.widget.LinearLayout.class).focusable(true).instance(2));
+ return listItem3.exists();
+ }
+
+ private void launchResolverActivity() throws UiObjectNotFoundException {
+ // Open KitchenSink > Activity Resolver
+ Intent intent = mInstrumentation
+ .getContext()
+ .getPackageManager()
+ .getLaunchIntentForPackage(KITCHEN_SINK_APP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("select", "activity resolver");
+ mInstrumentation.getContext().startActivity(intent);
+
+ UiObject button = mDevice.findObject(new UiSelector().resourceId(
+ TRIGGER_ACTIVITY_RESOLVER_RESOURCE_ID));
+ button.click();
+ mDevice.waitForIdle();
+ }
+
+ private void waitAndAssertFocused(UiObject view) throws UiObjectNotFoundException {
+ mDevice.wait(isViewFocused(view), WAIT_TIMEOUT_MS);
+ assertWithMessage("The view " + view + " should be focused")
+ .that(view.isFocused())
+ .isTrue();
+ }
+
+ private static Condition<UiDevice, Boolean> isViewFocused(UiObject view) {
+ return unusedDevice -> {
+ try {
+ return view.isFocused();
+ } catch (UiObjectNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ }
+}
diff --git a/builtInServices/tests/src/com/android/server/inputmethod/ImeSmokeTest.java b/builtInServices/tests/src/com/android/server/inputmethod/ImeSmokeTest.java
new file mode 100644
index 0000000..07abccf
--- /dev/null
+++ b/builtInServices/tests/src/com/android/server/inputmethod/ImeSmokeTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Instrumentation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.Condition;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * This is a simple smoke test to help finding culprit CLs when using bisect.
+ */
+public final class ImeSmokeTest {
+
+ private static final long KEYBOARD_LAUNCH_TIMEOUT = 5_000;
+
+ private static final long SWITCH_TO_HARD_IME_TIMEOUT_SECONDS = 5_000;
+
+ private static final String PLAIN_TEXT_EDIT_RESOURCE_ID =
+ "com.google.android.car.kitchensink:id/plain_text_edit";
+
+ private static final String KITCHEN_SINK_APP =
+ "com.google.android.car.kitchensink";
+
+ // Values of setting key SHOW_IME_WITH_HARD_KEYBOARD settings.
+ private static final int STATE_HIDE_IME = 0;
+ private static final int STATE_SHOW_IME = 1;
+
+ private static Instrumentation sInstrumentation;
+ private static UiDevice sDevice;
+ private static Context sContext;
+ private static ContentResolver sContentResolver;
+ private static int sOriginalShowImeWithHardKeyboard;
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sDevice = UiDevice.getInstance(sInstrumentation);
+ sContext = sInstrumentation.getContext();
+ sContentResolver = sContext.getContentResolver();
+
+ // Set this test to run on auto only, it was mostly designed to capture configuration
+ // issues on auto keyboards.
+ assumeTrue(sContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+
+ // Ensure that the DUT doesn't have hard keyboard enabled.
+ sOriginalShowImeWithHardKeyboard = Settings.Secure.getInt(sContentResolver,
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, /*def=*/STATE_SHOW_IME);
+ if (sOriginalShowImeWithHardKeyboard == STATE_HIDE_IME) {
+ assertThat(Settings.Secure.putInt(
+ sContentResolver, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ /*def=*/STATE_SHOW_IME)).isTrue();
+
+ // Give 5 seconds for IME to properly act on the settings change.
+ // TODO(b/301521594): Instead of sleeping, just verify the mShowImeWithHardKeyboard
+ // field from the current IME in IMMS.
+ SystemClock.sleep(SWITCH_TO_HARD_IME_TIMEOUT_SECONDS);
+ }
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ closeKitchenSink();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ closeKitchenSink();
+ }
+
+ @AfterClass
+ public static void tearDownClass() {
+ // Change back the original value of show_ime_with_hard_keyboard in Settings.
+ if (sOriginalShowImeWithHardKeyboard == STATE_HIDE_IME) {
+ assertThat(Settings.Secure.putInt(
+ sContentResolver, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ /*def=*/STATE_HIDE_IME)).isTrue();
+ }
+ }
+
+ private void closeKitchenSink() throws IOException {
+ sDevice.executeShellCommand(String.format("am force-stop %s", KITCHEN_SINK_APP));
+ }
+
+ @Test
+ public void canOpenIME() throws UiObjectNotFoundException {
+ // Open KitchenSink > Carboard
+ Intent intent = sInstrumentation
+ .getContext()
+ .getPackageManager()
+ .getLaunchIntentForPackage(KITCHEN_SINK_APP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("select", "carboard");
+ sContext.startActivity(intent);
+
+ UiObject editText = sDevice.findObject((new UiSelector().resourceId(
+ PLAIN_TEXT_EDIT_RESOURCE_ID)));
+ editText.click();
+
+ assertThat(sDevice.wait(isKeyboardOpened(), KEYBOARD_LAUNCH_TIMEOUT)).isTrue();
+ }
+
+ private static Condition<UiDevice, Boolean> isKeyboardOpened() {
+ return unusedDevice -> {
+ for (AccessibilityWindowInfo window : sInstrumentation.getUiAutomation().getWindows()) {
+ if (window.getType() == AccessibilityWindowInfo.TYPE_INPUT_METHOD) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+}
diff --git a/builtInServices/tests/src/com/android/server/wm/ActivityOptionsWrapperTest.java b/builtInServices/tests/src/com/android/server/wm/ActivityOptionsWrapperTest.java
index 682c31f..01bb696 100644
--- a/builtInServices/tests/src/com/android/server/wm/ActivityOptionsWrapperTest.java
+++ b/builtInServices/tests/src/com/android/server/wm/ActivityOptionsWrapperTest.java
@@ -20,6 +20,8 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityOptions;
+import androidx.test.filters.FlakyTest;
+
import org.junit.Test;
/**
@@ -27,6 +29,7 @@ import org.junit.Test;
*/
public final class ActivityOptionsWrapperTest {
@Test
+ @FlakyTest(bugId = 284947065)
public void create_returnsActivityOptionWrapper() {
ActivityOptions options = ActivityOptions.makeBasic();
ActivityOptionsWrapper wrapper = ActivityOptionsWrapper.create(options);
diff --git a/updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java b/updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java
index a079414..883a726 100644
--- a/updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java
+++ b/updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java
@@ -15,14 +15,8 @@
*/
package com.android.internal.car.updatable;
-import static android.view.Display.INVALID_DISPLAY;
-
import static com.android.car.internal.SystemConstants.ICAR_SYSTEM_SERVER_CLIENT;
import static com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE;
-import static com.android.car.internal.common.CommonConstants.INVALID_GID;
-import static com.android.car.internal.common.CommonConstants.INVALID_PID;
-import static com.android.car.internal.common.CommonConstants.INVALID_USER_ID;
-import static com.android.car.internal.util.VersionUtils.isPlatformVersionAtLeastU;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -54,6 +48,8 @@ import com.android.internal.car.CarServiceHelperInterface;
import com.android.internal.car.CarServiceHelperServiceUpdatable;
import com.android.server.wm.CarActivityInterceptorInterface;
import com.android.server.wm.CarActivityInterceptorUpdatableImpl;
+import com.android.server.wm.CarDisplayCompatScaleProviderInterface;
+import com.android.server.wm.CarDisplayCompatScaleProviderUpdatableImpl;
import com.android.server.wm.CarLaunchParamsModifierInterface;
import com.android.server.wm.CarLaunchParamsModifierUpdatable;
import com.android.server.wm.CarLaunchParamsModifierUpdatableImpl;
@@ -61,6 +57,7 @@ import com.android.server.wm.CarLaunchParamsModifierUpdatableImpl;
import java.io.File;
import java.io.PrintWriter;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@@ -108,48 +105,36 @@ public final class CarServiceHelperServiceUpdatableImpl
private final CarLaunchParamsModifierUpdatableImpl mCarLaunchParamsModifierUpdatable;
private final CarActivityInterceptorUpdatableImpl mCarActivityInterceptorUpdatable;
+ private final CarDisplayCompatScaleProviderUpdatableImpl
+ mCarDisplayCompatScaleProviderUpdatable;
/**
* This constructor is meant to be called using reflection by the builtin service and hence it
* shouldn't be changed as it is called from the platform with version {@link TIRAMISU}.
*/
public CarServiceHelperServiceUpdatableImpl(Context context,
- CarServiceHelperInterface carServiceHelperInterface,
- CarLaunchParamsModifierInterface carLaunchParamsModifierInterface) {
- this(context, carServiceHelperInterface, carLaunchParamsModifierInterface,
- /* carActivityInterceptorInterface= */ null);
- }
-
- public CarServiceHelperServiceUpdatableImpl(Context context,
- CarServiceHelperInterface carServiceHelperInterface,
- CarLaunchParamsModifierInterface carLaunchParamsModifierInterface,
- CarActivityInterceptorInterface carActivityInterceptorInterface) {
- this(context, carServiceHelperInterface, carLaunchParamsModifierInterface,
- carActivityInterceptorInterface, /* carServiceProxy= */ null);
- }
-
- @VisibleForTesting
- CarServiceHelperServiceUpdatableImpl(Context context,
- CarServiceHelperInterface carServiceHelperInterface,
- CarLaunchParamsModifierInterface carLaunchParamsModifierInterface,
- @Nullable CarActivityInterceptorInterface carActivityInterceptorInterface,
- @Nullable CarServiceProxy carServiceProxy) {
+ @Nullable Map<String, Object> interfaces) {
mContext = context;
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
- mCarServiceHelperInterface = carServiceHelperInterface;
+ mCarServiceHelperInterface = (CarServiceHelperInterface) interfaces
+ .get(CarServiceHelperInterface.class.getSimpleName());
mCarLaunchParamsModifierUpdatable = new CarLaunchParamsModifierUpdatableImpl(
- carLaunchParamsModifierInterface);
- if (isPlatformVersionAtLeastU()) {
- mCarActivityInterceptorUpdatable = new CarActivityInterceptorUpdatableImpl(
- (CarActivityInterceptorInterface) carActivityInterceptorInterface);
- } else {
- mCarActivityInterceptorUpdatable = null;
- }
+ (CarLaunchParamsModifierInterface) interfaces
+ .get(CarLaunchParamsModifierInterface.class.getSimpleName()));
+ mCarActivityInterceptorUpdatable = new CarActivityInterceptorUpdatableImpl(
+ (CarActivityInterceptorInterface) interfaces
+ .get(CarActivityInterceptorInterface.class.getSimpleName()));
+ mCarDisplayCompatScaleProviderUpdatable =
+ new CarDisplayCompatScaleProviderUpdatableImpl(
+ mContext,
+ (CarDisplayCompatScaleProviderInterface) interfaces
+ .get(CarDisplayCompatScaleProviderInterface.class.getSimpleName()));
// carServiceProxy is Nullable because it is not possible to construct carServiceProxy with
// "this" object in the previous constructor as CarServiceHelperServiceUpdatableImpl has
// not been fully constructed.
- mCarServiceProxy = carServiceProxy == null ? new CarServiceProxy(this) : carServiceProxy;
+ mCarServiceProxy = (CarServiceProxy) interfaces.getOrDefault(
+ CarServiceProxy.class.getSimpleName(), new CarServiceProxy(this));
mCallbackForCarServiceUnresponsiveness = () -> handleCarServiceUnresponsive();
}
@@ -213,6 +198,12 @@ public final class CarServiceHelperServiceUpdatableImpl
return mCarActivityInterceptorUpdatable;
}
+ @Override
+ public CarDisplayCompatScaleProviderUpdatableImpl
+ getCarDisplayCompatScaleProviderUpdatable() {
+ return mCarDisplayCompatScaleProviderUpdatable;
+ }
+
@VisibleForTesting
void handleCarServiceConnection(IBinder iBinder) {
synchronized (mLock) {
@@ -312,6 +303,7 @@ public final class CarServiceHelperServiceUpdatableImpl
mCarServiceProxy.dump(pw);
mCarLaunchParamsModifierUpdatable.dump(pw);
mCarActivityInterceptorUpdatable.dump(pw);
+ mCarDisplayCompatScaleProviderUpdatable.dump(pw);
}
@VisibleForTesting
@@ -328,13 +320,6 @@ public final class CarServiceHelperServiceUpdatableImpl
}
@Override
- public void setSourcePreferredComponents(boolean enableSourcePreferred,
- @Nullable List<ComponentName> sourcePreferredComponents) {
- mCarLaunchParamsModifierUpdatable.setSourcePreferredComponents(
- enableSourcePreferred, sourcePreferredComponents);
- }
-
- @Override
public int setPersistentActivity(ComponentName activity, int displayId, int featureId) {
return mCarLaunchParamsModifierUpdatable.setPersistentActivity(
activity, displayId, featureId);
@@ -364,59 +349,43 @@ public final class CarServiceHelperServiceUpdatableImpl
@Override
public void setProcessGroup(int pid, int group) {
- if (!isPlatformVersionAtLeastU()) {
- return;
- }
mCarServiceHelperInterface.setProcessGroup(pid, group);
}
@Override
public int getProcessGroup(int pid) {
- if (isPlatformVersionAtLeastU()) {
- return mCarServiceHelperInterface.getProcessGroup(pid);
- }
- return INVALID_GID;
+ return mCarServiceHelperInterface.getProcessGroup(pid);
}
@Override
public int getMainDisplayAssignedToUser(int userId) {
- if (isPlatformVersionAtLeastU()) {
- return mCarServiceHelperInterface.getMainDisplayAssignedToUser(userId);
- }
- return INVALID_DISPLAY;
+ return mCarServiceHelperInterface.getMainDisplayAssignedToUser(userId);
}
@Override
public int getUserAssignedToDisplay(int displayId) {
- if (isPlatformVersionAtLeastU()) {
- return mCarServiceHelperInterface.getUserAssignedToDisplay(displayId);
- }
- return INVALID_USER_ID;
+ return mCarServiceHelperInterface.getUserAssignedToDisplay(displayId);
}
@Override
public boolean startUserInBackgroundVisibleOnDisplay(int userId, int displayId) {
- if (isPlatformVersionAtLeastU()) {
- return mCarServiceHelperInterface.startUserInBackgroundVisibleOnDisplay(
- userId, displayId);
- }
- return false;
+ return mCarServiceHelperInterface.startUserInBackgroundVisibleOnDisplay(
+ userId, displayId);
}
@Override
public void setProcessProfile(int pid, int uid, @NonNull String profile) {
- if (!isPlatformVersionAtLeastU()) {
- return;
- }
mCarServiceHelperInterface.setProcessProfile(pid, uid, profile);
}
@Override
public int fetchAidlVhalPid() {
- if (isPlatformVersionAtLeastU()) {
- return mCarServiceHelperInterface.fetchAidlVhalPid();
- }
- return INVALID_PID;
+ return mCarServiceHelperInterface.fetchAidlVhalPid();
+ }
+
+ @Override
+ public boolean requiresDisplayCompat(String packageName) {
+ return mCarDisplayCompatScaleProviderUpdatable.requiresDisplayCompat(packageName);
}
}
diff --git a/updatableServices/src/com/android/server/wm/CarActivityInterceptorUpdatableImpl.java b/updatableServices/src/com/android/server/wm/CarActivityInterceptorUpdatableImpl.java
index 1cb925d..21a92e9 100644
--- a/updatableServices/src/com/android/server/wm/CarActivityInterceptorUpdatableImpl.java
+++ b/updatableServices/src/com/android/server/wm/CarActivityInterceptorUpdatableImpl.java
@@ -16,10 +16,9 @@
package com.android.server.wm;
-import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.view.Display.INVALID_DISPLAY;
import android.annotation.NonNull;
-import android.annotation.RequiresApi;
import android.annotation.SystemApi;
import android.app.ActivityOptions;
import android.car.builtin.util.Slogf;
@@ -28,6 +27,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -43,10 +43,10 @@ import java.util.Set;
*
* @hide
*/
-@RequiresApi(UPSIDE_DOWN_CAKE)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class CarActivityInterceptorUpdatableImpl implements CarActivityInterceptorUpdatable {
public static final String TAG = CarActivityInterceptorUpdatableImpl.class.getSimpleName();
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -79,6 +79,13 @@ public final class CarActivityInterceptorUpdatableImpl implements CarActivityInt
if (optionsWrapper == null) {
optionsWrapper = ActivityOptionsWrapper.create(ActivityOptions.makeBasic());
}
+
+ // Even if the activity is assigned a root task to open in, the launch display ID
+ // should take preference when opening the activity. More details in b/295893892.
+ if (!isRootTaskDisplayIdSameAsLaunchDisplayId(rootTaskToken, optionsWrapper)) {
+ return null;
+ }
+
optionsWrapper.setLaunchRootTask(rootTaskToken);
return ActivityInterceptResultWrapper.create(info.getIntent(),
optionsWrapper.getOptions());
@@ -87,18 +94,52 @@ public final class CarActivityInterceptorUpdatableImpl implements CarActivityInt
return null;
}
+ private boolean isRootTaskDisplayIdSameAsLaunchDisplayId(IBinder rootTaskToken,
+ ActivityOptionsWrapper optionsWrapper) {
+ int launchDisplayId = optionsWrapper.getOptions().getLaunchDisplayId();
+ if (launchDisplayId == INVALID_DISPLAY) {
+ if (DBG) {
+ Slogf.d(TAG,
+ "The launch display Id of the activity is unset, let it open root task");
+ }
+ return true;
+ }
+ TaskWrapper rootTask = TaskWrapper.createFromToken(rootTaskToken);
+ int rootTaskDisplayId = rootTask.getTaskDisplayArea().getDisplay().getDisplayId();
+ if (launchDisplayId == rootTaskDisplayId) {
+ if (DBG) {
+ Slogf.d(TAG, "The launch display Id of the activity is (%d)", launchDisplayId);
+ }
+ return true;
+ }
+ if (DBG) {
+ Slogf.d(TAG,
+ "The launch display Id (%d) of the activity doesn't match the display Id (%d)"
+ + " (which the root task is added in).",
+ launchDisplayId, rootTaskDisplayId);
+ }
+ return false;
+ }
+
private boolean isRootTaskUserSameAsActivityUser(IBinder rootTaskToken,
ActivityInterceptorInfoWrapper activityInterceptorInfoWrapper) {
TaskWrapper rootTask = TaskWrapper.createFromToken(rootTaskToken);
+ if (rootTask == null) {
+ Slogf.w(TAG, "Root task not found.");
+ return false;
+ }
int userIdFromActivity = activityInterceptorInfoWrapper.getUserId();
int userIdFromRootTask = mBuiltIn.getUserAssignedToDisplay(rootTask
.getTaskDisplayArea().getDisplay().getDisplayId());
if (userIdFromActivity == userIdFromRootTask) {
return true;
}
- Slogf.w(TAG, "The user id of launched activity (%d) doesn't match the "
- + "user id which the display (which the root task is added in) is "
- + "assigned to (%d).", userIdFromActivity, userIdFromRootTask);
+ if (DBG) {
+ Slogf.d(TAG,
+ "The user id of launched activity (%d) doesn't match the user id which the "
+ + "display (which the root task is added in) is assigned to (%d).",
+ userIdFromActivity, userIdFromRootTask);
+ }
return false;
}
diff --git a/updatableServices/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatableImpl.java b/updatableServices/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatableImpl.java
new file mode 100644
index 0000000..56886f3
--- /dev/null
+++ b/updatableServices/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatableImpl.java
@@ -0,0 +1,268 @@
+/*
+ * 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;
+
+import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
+import android.car.builtin.util.Slogf;
+import android.content.Context;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.res.CompatScaleWrapper;
+import android.os.Environment;
+import android.os.ServiceSpecificException;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.car.internal.util.IndentingPrintWriter;
+import com.android.internal.annotations.GuardedBy;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Implementation of {@link CarDisplayCompatScaleProviderUpdatable}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class CarDisplayCompatScaleProviderUpdatableImpl implements
+ CarDisplayCompatScaleProviderUpdatable {
+ private static final String TAG = "CarDisplayCompatScaleProvider";
+ private static final String AUTOENHANCE_SYSTEM_FEATURE = "android.car.displaycompatibility";
+ private static final String CONFIG_PATH = "etc/display_compat_config.xml";
+ private static final String NS = null;
+
+ private final Object mLock = new Object();
+ private final PackageManager mPackageManager;
+ private final CarDisplayCompatScaleProviderInterface mCarCompatScaleProviderInterface;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<String, Boolean> mRequiresAutoEnhance = new ArrayMap<>();
+
+ private Config mConfig = new Config();
+
+ public CarDisplayCompatScaleProviderUpdatableImpl(Context context,
+ CarDisplayCompatScaleProviderInterface carCompatScaleProviderInterface) {
+ mPackageManager = context.getPackageManager();
+ mCarCompatScaleProviderInterface = carCompatScaleProviderInterface;
+
+ // TODO(b/300505673): remove once Chrome is ready
+ mRequiresAutoEnhance.put("com.android.chrome", true);
+
+ try (FileInputStream in = getConfigFile().openRead();) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
+ parser.setInput(in, null);
+ parser.nextTag();
+ mConfig.readConfig(parser);
+ } catch (XmlPullParserException | IOException | SecurityException e) {
+ Slogf.e(TAG, "read config failed", e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public CompatScaleWrapper getCompatScale(@NonNull String packageName, @UserIdInt int userId) {
+ try {
+ if (requiresDisplayCompat(packageName)) {
+ int display = mCarCompatScaleProviderInterface.getMainDisplayAssignedToUser(userId);
+ if (display == INVALID_DISPLAY) {
+ display = DEFAULT_DISPLAY;
+ }
+ float scale = mConfig.get(display, 1.0f);
+ return new CompatScaleWrapper(1.0f, scale);
+ }
+ } catch (ServiceSpecificException e) {
+ return null;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean requiresDisplayCompat(@NonNull String packageName) {
+ boolean result = false;
+ synchronized (mLock) {
+ // TODO(b/300642384): need to listen to add/remove of packages from PackageManager so
+ // the list doesn't have stale data.
+ Boolean res = mRequiresAutoEnhance.get(packageName);
+ if (res != null) {
+ return res.booleanValue();
+ }
+
+ try {
+ PackageInfoFlags flags = PackageInfoFlags.of(GET_CONFIGURATIONS);
+ FeatureInfo[] features = mPackageManager.getPackageInfo(packageName, flags)
+ .reqFeatures;
+ if (features != null) {
+ for (FeatureInfo feature: features) {
+ // TODO: get the string from PackageManager
+ if (AUTOENHANCE_SYSTEM_FEATURE.equals(feature.name)) {
+ Slogf.i(TAG, "detected autoenhance package: " + packageName);
+ result = true;
+ break;
+ }
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slogf.e(TAG, "Package " + packageName + " not found", e);
+ throw new ServiceSpecificException(
+ -100 /** {@code CarPackageManager#ERROR_CODE_NO_PACKAGE} */,
+ e.getMessage());
+ }
+
+ mRequiresAutoEnhance.put(packageName, result);
+ }
+ return result;
+ }
+
+ /**
+ * Dump {code CarDisplayCompatScaleProviderUpdatableImpl#mRequiresAutoEnhance}
+ */
+ public void dump(IndentingPrintWriter writer) {
+ writer.println(TAG);
+ writer.increaseIndent();
+ writer.println("AutoEnhance Config:");
+ writer.increaseIndent();
+ if (mConfig.size() == 0) {
+ writer.println("Config is empty.");
+ } else {
+ for (int i = 0; i < mConfig.size(); i++) {
+ float scale = mConfig.get(i, -1);
+ if (scale != -1) {
+ writer.println("display: " + i + " scale: " + scale);
+ }
+ }
+ }
+ writer.decreaseIndent();
+ writer.println("List of AutoEnhance packages:");
+ writer.increaseIndent();
+ synchronized (mLock) {
+ if (mRequiresAutoEnhance.size() == 0) {
+ writer.println("No package is enabled.");
+ } else {
+ for (int i = 0; i < mRequiresAutoEnhance.size(); i++) {
+ if (mRequiresAutoEnhance.valueAt(i)) {
+ writer.println("Package name: " + mRequiresAutoEnhance.keyAt(i));
+ }
+ }
+ }
+ }
+ writer.decreaseIndent();
+ writer.decreaseIndent();
+ }
+
+ @NonNull
+ private static AtomicFile getConfigFile() {
+ File configFile = new File(Environment.getProductDirectory(), CONFIG_PATH);
+ return new AtomicFile(configFile);
+ }
+
+ private static class Scale {
+ public final int display;
+ public final float scale;
+
+ private Scale(int display, float scale) {
+ this.display = display;
+ this.scale = scale;
+ }
+ }
+
+ private static class Config {
+ private static final String CONFIG = "config";
+ private static final String SCALE = "scale";
+ private static final String DISPLAY = "display";
+
+ private SparseArray<Float> mScales = new SparseArray<>();
+
+ public int size() {
+ return mScales.size();
+ }
+
+ public float get(int index, float defaultValue) {
+ return mScales.get(index, defaultValue);
+ }
+
+ public void readConfig(@NonNull XmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ parser.require(XmlPullParser.START_TAG, NS, CONFIG);
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+ if (SCALE.equals(name)) {
+ Scale scale = readScale(parser);
+ mScales.put(scale.display, scale.scale);
+ } else {
+ skip(parser);
+ }
+ }
+ }
+
+ private Scale readScale(@NonNull XmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ parser.require(XmlPullParser.START_TAG, NS, SCALE);
+ int display = DEFAULT_DISPLAY;
+ try {
+ display = Integer.parseInt(parser.getAttributeValue(NS, DISPLAY));
+ } catch (NullPointerException | NumberFormatException e) {
+ Slogf.e(TAG, "parse failed: " + parser.getAttributeValue(NS, DISPLAY), e);
+ }
+ float value = 1f;
+ if (parser.next() == XmlPullParser.TEXT) {
+ try {
+ value = Float.parseFloat(parser.getText());
+ } catch (NullPointerException | NumberFormatException e) {
+ Slogf.e(TAG, "parse failed: " + parser.getText(), e);
+ }
+ parser.nextTag();
+ }
+ parser.require(XmlPullParser.END_TAG, NS, SCALE);
+ return new Scale(display, value);
+ }
+
+ private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException();
+ }
+ int depth = 1;
+ while (depth != 0) {
+ switch (parser.next()) {
+ case XmlPullParser.END_TAG:
+ depth--;
+ break;
+ case XmlPullParser.START_TAG:
+ depth++;
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java b/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java
index 0be52fb..1991797 100644
--- a/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java
+++ b/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java
@@ -16,13 +16,10 @@
package com.android.server.wm;
-import static android.car.PlatformVersion.VERSION_CODES;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
-import android.car.PlatformVersionMismatchException;
import android.car.app.CarActivityManager;
import android.car.builtin.os.UserManagerHelper;
import android.car.builtin.util.Slogf;
@@ -38,7 +35,6 @@ import android.util.SparseIntArray;
import android.view.Display;
import com.android.car.internal.util.IndentingPrintWriter;
-import com.android.car.internal.util.VersionUtils;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
@@ -133,26 +129,6 @@ public final class CarLaunchParamsModifierUpdatableImpl
}
}
- /**
- * Sets {@code sourcePreferred} configuration. When {@code sourcePreferred} is enabled and
- * there is no pre-assigned display for the Activity, CarLauncherParamsModifier will launch
- * the Activity in the display of the source. When {@code sourcePreferredComponents} isn't null
- * the {@code sourcePreferred} is applied for the {@code sourcePreferredComponents} only.
- *
- * @param enableSourcePreferred whether to enable sourcePreferred mode
- * @param sourcePreferredComponents null for all components, or the list of components to apply
- */
- public void setSourcePreferredComponents(boolean enableSourcePreferred,
- @Nullable List<ComponentName> sourcePreferredComponents) {
- synchronized (mLock) {
- mIsSourcePreferred = enableSourcePreferred;
- mSourcePreferredComponents = sourcePreferredComponents;
- if (mSourcePreferredComponents != null) {
- Collections.sort(mSourcePreferredComponents);
- }
- }
- }
-
@Override
public void handleUserVisibilityChanged(int userId, boolean visible) {
synchronized (mLock) {
@@ -169,9 +145,6 @@ public final class CarLaunchParamsModifierUpdatableImpl
}
private int getCurrentOrTargetUserId() {
- if (!VersionUtils.isPlatformVersionAtLeastU()) {
- throw new PlatformVersionMismatchException(VERSION_CODES.UPSIDE_DOWN_CAKE_0);
- }
Pair<Integer, Integer> currentAndTargetUserIds = mBuiltin.getCurrentAndTargetUserIds();
int currentUserId = currentAndTargetUserIds.first;
int targetUserId = currentAndTargetUserIds.second;
@@ -404,8 +377,7 @@ public final class CarLaunchParamsModifierUpdatableImpl
Slogf.i(TAG, "Changed launching display, user:%d requested display area:%s"
+ " target display area:%s", userId, originalDisplayArea, targetDisplayArea);
outParams.setPreferredTaskDisplayArea(targetDisplayArea);
- if (VersionUtils.isPlatformVersionAtLeastU()
- && options != null
+ if (options != null
&& options.getLaunchWindowingMode()
!= ActivityOptionsWrapper.WINDOWING_MODE_UNDEFINED) {
outParams.setWindowingMode(options.getLaunchWindowingMode());
@@ -423,10 +395,7 @@ public final class CarLaunchParamsModifierUpdatableImpl
if (userForDisplay != UserManagerHelper.USER_NULL) {
return userForDisplay;
}
- if (VersionUtils.isPlatformVersionAtLeastU()) {
- userForDisplay = mBuiltin.getUserAssignedToDisplay(displayId);
- }
- return userForDisplay;
+ return mBuiltin.getUserAssignedToDisplay(displayId);
}
@GuardedBy("mLock")
@@ -473,14 +442,13 @@ public final class CarLaunchParamsModifierUpdatableImpl
int displayId = mDefaultDisplayForProfileUser.get(userId);
return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId);
}
- if (VersionUtils.isPlatformVersionAtLeastU()) {
- int displayId = mBuiltin.getMainDisplayAssignedToUser(userId);
- if (displayId != Display.INVALID_DISPLAY) {
- return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId);
- }
+ int displayId = mBuiltin.getMainDisplayAssignedToUser(userId);
+ if (displayId != Display.INVALID_DISPLAY) {
+ return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId);
}
+
if (!mPassengerDisplays.isEmpty()) {
- int displayId = mPassengerDisplays.get(0);
+ displayId = mPassengerDisplays.get(0);
if (DBG) {
Slogf.d(TAG, "fallbackDisplayAreaForUserLocked: userId=%d, displayId=%d",
userId, displayId);
diff --git a/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImplTest.java b/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImplTest.java
index d608e12..42017d1 100644
--- a/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImplTest.java
+++ b/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImplTest.java
@@ -35,12 +35,13 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.car.internal.util.VersionUtils;
import com.android.internal.car.CarServiceHelperInterface;
import com.android.server.wm.CarActivityInterceptorInterface;
+import com.android.server.wm.CarDisplayCompatScaleProviderInterface;
import com.android.server.wm.CarLaunchParamsModifierInterface;
import org.junit.Before;
@@ -49,6 +50,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoSession;
+import java.util.Map;
import java.util.function.BiConsumer;
/**
@@ -71,6 +73,9 @@ public final class CarServiceHelperServiceUpdatableImplTest
@Mock
private CarActivityInterceptorInterface mCarActivityInterceptorInterface;
@Mock
+ private CarDisplayCompatScaleProviderInterface
+ mCarDisplayCompatScaleProviderInterface;
+ @Mock
private ICar mICarBinder;
@Mock
private IBinder mIBinder;
@@ -83,18 +88,20 @@ public final class CarServiceHelperServiceUpdatableImplTest
@Before
public void setTestFixtures() {
+ Map<String, Object> interfaces = new ArrayMap<>();
+ interfaces.put(CarServiceHelperInterface.class.getSimpleName(),
+ mCarServiceHelperInterface);
+ interfaces.put(CarLaunchParamsModifierInterface.class.getSimpleName(),
+ mCarLaunchParamsModifierInterface);
+ interfaces.put(CarActivityInterceptorInterface.class.getSimpleName(),
+ mCarActivityInterceptorInterface);
+ interfaces.put(CarDisplayCompatScaleProviderInterface.class.getSimpleName(),
+ mCarDisplayCompatScaleProviderInterface);
+ interfaces.put(CarServiceProxy.class.getSimpleName(), mCarServiceProxy);
+
mCarServiceHelperServiceUpdatableImpl = new CarServiceHelperServiceUpdatableImpl(
mMockContext,
- mCarServiceHelperInterface,
- mCarLaunchParamsModifierInterface,
- mCarActivityInterceptorInterface,
- mCarServiceProxy);
- }
-
- @Override
- protected void onSessionBuilder(
- AbstractExtendedMockitoTestCase.CustomMockitoSessionBuilder builder) {
- builder.spyStatic(VersionUtils.class);
+ interfaces);
}
@Test
diff --git a/updatableServices/tests/src/com/android/server/wm/CarActivityInterceptorUpdatableTest.java b/updatableServices/tests/src/com/android/server/wm/CarActivityInterceptorUpdatableTest.java
index 339616b..0a28662 100644
--- a/updatableServices/tests/src/com/android/server/wm/CarActivityInterceptorUpdatableTest.java
+++ b/updatableServices/tests/src/com/android/server/wm/CarActivityInterceptorUpdatableTest.java
@@ -45,6 +45,8 @@ import java.util.List;
public class CarActivityInterceptorUpdatableTest {
private static final int DEFAULT_CURRENT_USER_ID = 112;
private static final int PASSENGER_USER_ID = 198;
+ private static final int DISPLAY_ID_1 = 0;
+ private static final int DISPLAY_ID_2 = 2;
private CarActivityInterceptorUpdatableImpl mInterceptor;
private MockitoSession mMockingSession;
private WindowContainer.RemoteToken mRootTaskToken1;
@@ -56,13 +58,19 @@ public class CarActivityInterceptorUpdatableTest {
private Task mWindowContainer2;
@Mock
- private DisplayContent mDisplayContent;
+ private DisplayContent mDisplayContent1;
+ @Mock
+ private DisplayContent mDisplayContent2;
@Mock
- private Display mDisplay;
+ private Display mDisplay1;
+ @Mock
+ private Display mDisplay2;
@Mock
- private TaskDisplayArea mTda;
+ private TaskDisplayArea mTda1;
+ @Mock
+ private TaskDisplayArea mTda2;
private final CarActivityInterceptorInterface mCarActivityInterceptorInterface =
new CarActivityInterceptorInterface() {
@@ -84,16 +92,22 @@ public class CarActivityInterceptorUpdatableTest {
.strictness(Strictness.LENIENT)
.startMocking();
- mTda.mDisplayContent = mDisplayContent;
- when(mDisplayContent.getDisplay()).thenReturn(mDisplay);
- when(mDisplay.getDisplayId()).thenReturn(0);
+ mTda1.mDisplayContent = mDisplayContent1;
+ when(mDisplayContent1.getDisplay()).thenReturn(mDisplay1);
+ when(mTda1.getDisplayId()).thenReturn(DISPLAY_ID_1);
+ when(mDisplay1.getDisplayId()).thenReturn(DISPLAY_ID_1);
mRootTaskToken1 = new WindowContainer.RemoteToken(mWindowContainer1);
mWindowContainer1.mRemoteToken = mRootTaskToken1;
- when(mWindowContainer1.getTaskDisplayArea()).thenReturn(mTda);
+ when(mWindowContainer1.getTaskDisplayArea()).thenReturn(mTda1);
+
+ when(mDisplayContent2.getDisplay()).thenReturn(mDisplay2);
+ when(mTda2.getDisplayId()).thenReturn(DISPLAY_ID_2);
+ when(mDisplay2.getDisplayId()).thenReturn(DISPLAY_ID_2);
+ mTda2.mDisplayContent = mDisplayContent2;
mRootTaskToken2 = new WindowContainer.RemoteToken(mWindowContainer2);
- when(mWindowContainer2.getTaskDisplayArea()).thenReturn(mTda);
+ when(mWindowContainer2.getTaskDisplayArea()).thenReturn(mTda2);
mWindowContainer2.mRemoteToken = mRootTaskToken2;
mInterceptor = new CarActivityInterceptorUpdatableImpl(mCarActivityInterceptorInterface);
@@ -206,6 +220,24 @@ public class CarActivityInterceptorUpdatableTest {
}
@Test
+ public void interceptActivityLaunch_persistedActivity_differentLaunchDisplayId_returnsNull() {
+ List<ComponentName> activities = List.of(
+ ComponentName.unflattenFromString("com.example.app/com.example.app.MainActivity")
+ );
+ mInterceptor.setPersistentActivityOnRootTask(activities, mRootTaskToken2);
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(DISPLAY_ID_1);
+
+ ActivityInterceptorInfoWrapper info =
+ createActivityInterceptorInfoWithMainIntent(activities.get(0).getPackageName(),
+ activities.get(0).getClassName(), /* options= */ options);
+
+ ActivityInterceptResultWrapper result =
+ mInterceptor.onInterceptActivityLaunch(info);
+ assertThat(result).isNull();
+ }
+
+ @Test
public void interceptActivityLaunch_persistedActivity_setsLaunchRootTask() {
List<ComponentName> activities = List.of(
ComponentName.unflattenFromString("com.example.app/com.example.app.MainActivity"),
diff --git a/updatableServices/tests/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatableTest.java b/updatableServices/tests/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatableTest.java
new file mode 100644
index 0000000..ece50d9
--- /dev/null
+++ b/updatableServices/tests/src/com/android/server/wm/CarDisplayCompatScaleProviderUpdatableTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PackageInfoFlags;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class CarDisplayCompatScaleProviderUpdatableTest {
+ private CarDisplayCompatScaleProviderUpdatableImpl mImpl;
+ private MockitoSession mMockingSession;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PackageInfo mPackageInfo;
+
+ private final CarDisplayCompatScaleProviderInterface mInterface =
+ new CarDisplayCompatScaleProviderInterface() {
+ @Override
+ public int getMainDisplayAssignedToUser(int userId) {
+ return DEFAULT_DISPLAY;
+ }
+ };
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ mImpl = new CarDisplayCompatScaleProviderUpdatableImpl(mContext, mInterface);
+ }
+
+ @After
+ public void tearDown() {
+ // If the exception is thrown during the MockingSession setUp, mMockingSession can be null.
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ @Test
+ public void requiresDisplayCompat_returnsTrue() throws NameNotFoundException {
+ FeatureInfo[] features = new FeatureInfo[1];
+ features[0] = new FeatureInfo();
+ features[0].name = "android.car.displaycompatibility";
+ mPackageInfo.reqFeatures = features;
+ when(mPackageManager.getPackageInfo(eq("package1"), any(PackageInfoFlags.class)))
+ .thenReturn(mPackageInfo);
+
+ assertThat(mImpl.requiresDisplayCompat("package1")).isTrue();
+ }
+
+ @Test
+ public void requiresDisplayCompat_returnsFalse() throws NameNotFoundException {
+ when(mPackageManager.getPackageInfo(eq("package1"), any(PackageInfoFlags.class)))
+ .thenReturn(mPackageInfo);
+ assertThat(mImpl.requiresDisplayCompat("package1")).isFalse();
+ }
+
+ @Test
+ public void requiresDisplayCompat_packageStateIsCached() throws NameNotFoundException {
+ FeatureInfo[] features = new FeatureInfo[1];
+ features[0] = new FeatureInfo();
+ features[0].name = "android.car.displaycompatibility";
+ mPackageInfo.reqFeatures = features;
+ when(mPackageManager.getPackageInfo(eq("package1"), any(PackageInfoFlags.class)))
+ .thenReturn(mPackageInfo);
+ mImpl.requiresDisplayCompat("package1");
+
+ assertThat(mImpl.requiresDisplayCompat("package1")).isTrue();
+ // Verify the number of calls to PackageManager#getPackageInfo did not increase.
+ verify(mPackageManager, times(1)).getPackageInfo(eq("package1"),
+ any(PackageInfoFlags.class));
+ }
+}
diff --git a/updatableServices/tests/src/com/android/server/wm/CarLaunchParamsModifierUpdatableTest.java b/updatableServices/tests/src/com/android/server/wm/CarLaunchParamsModifierUpdatableTest.java
index e59f9f9..38418a5 100644
--- a/updatableServices/tests/src/com/android/server/wm/CarLaunchParamsModifierUpdatableTest.java
+++ b/updatableServices/tests/src/com/android/server/wm/CarLaunchParamsModifierUpdatableTest.java
@@ -73,7 +73,6 @@ import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
-import java.util.Arrays;
import java.util.function.Function;
/**
@@ -538,62 +537,6 @@ public class CarLaunchParamsModifierUpdatableTest {
}
@Test
- public void testPreferSourceForDriver() {
- when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver);
-
- // When no sourcePreferredComponents is set, it doesn't set the display for system user.
- assertNoDisplayIsAssigned(UserHandle.USER_SYSTEM);
-
- mUpdatable.setSourcePreferredComponents(true, null);
- assertDisplayIsAssigned(UserHandle.USER_SYSTEM, mDisplayArea0ForDriver);
- }
-
- @Test
- public void testPreferSourceForPassenger() {
- mUpdatable.setPassengerDisplays(
- new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11});
- int passengerUserId = 100;
- mUpdatable.setDisplayAllowListForUser(passengerUserId,
- new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11});
- when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea11ForPassenger);
-
- // When no sourcePreferredComponents is set, it returns the default passenger display.
- assertDisplayIsAssigned(passengerUserId, mDisplayArea10ForPassenger);
-
- mUpdatable.setSourcePreferredComponents(true, null);
- assertDisplayIsAssigned(passengerUserId, mDisplayArea11ForPassenger);
- }
-
- @Test
- public void testPreferSourceDoNotOverrideActivityOptions() {
- when(mActivityOptions.getLaunchDisplayId()).thenReturn(PASSENGER_DISPLAY_ID_10);
- when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver);
-
- mUpdatable.setSourcePreferredComponents(true, null);
- assertNoDisplayIsAssigned(UserHandle.USER_SYSTEM);
- }
-
- @Test
- public void testPreferSourceForSpecifiedActivity() {
- when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver);
- mActivityRecordActivity = buildActivityRecord("testPackage", "testActivity");
- mUpdatable.setSourcePreferredComponents(true,
- Arrays.asList(new ComponentName("testPackage", "testActivity")));
-
- assertDisplayIsAssigned(UserHandle.USER_SYSTEM, mDisplayArea0ForDriver);
- }
-
- @Test
- public void testPreferSourceDoNotAssignDisplayForNonSpecifiedActivity() {
- when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver);
- mActivityRecordActivity = buildActivityRecord("placeholderPackage", "placeholderActivity");
- mUpdatable.setSourcePreferredComponents(true,
- Arrays.asList(new ComponentName("testPackage", "testActivity")));
-
- assertNoDisplayIsAssigned(UserHandle.USER_SYSTEM);
- }
-
- @Test
public void testEmbeddedActivityCanLaunchOnVirtualDisplay() {
// The launch request comes from the Activity in Virtual display.
DisplayContent dc = mRootWindowContainer.getDisplayContentOrCreate(VIRTUAL_DISPLAY_ID_2);