diff options
author | Xin Li <delphij@google.com> | 2024-01-23 20:59:49 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2024-01-23 20:59:49 -0800 |
commit | c5116e49a930b7463209454957089b2763e7c228 (patch) | |
tree | 8d9bffe56d97a1cd7abdcbc7316d0cdbcf03a96b | |
parent | b3a35397fa2d546ffa8ef88d83f3b1953d079fd0 (diff) | |
parent | a57606054c0e0dd2f6c07ff3cb26273ebed072e5 (diff) | |
download | services-c5116e49a930b7463209454957089b2763e7c228.tar.gz |
Merge Android 24Q1 Release (ab/11220357)
Bug: 319669529
Merged-In: Idbb90ab8ac60df7242add7f53559433e4abb5fc9
Change-Id: I74b3d6da9741fffadbb94e488f54bc29bd3408c4
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 Binary files differdeleted file mode 100644 index 0d4c93e..0000000 --- a/builtInServices/prebuilts/mu_imms-prebuilt.jar +++ /dev/null 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 c679b93..3d073fd 100644 --- a/builtInServices/tests/Android.bp +++ b/builtInServices/tests/Android.bp @@ -32,6 +32,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); |