From 49cd50b3fd7e8774dbd15352b3704bb6b9767e8c Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Mon, 29 Mar 2021 15:53:31 +0800 Subject: [PATCH] Introduce WindowProviderService A Window Provider Service is a Window-Context-like Service which handles UI components and is able to obtain the latest configuration. The differences between a Window Context and a Window Provider Service is that: 1. It is always associated with the primary display before attachWindowToken() or WM#addView is called. It is suggested to render UI components after calling the APIs mentioned above. 2. A window context registers the listener in constructor and unregisters it in finalize(), while a window provider service registers the listener in onCreate() and unregisters in onDestroy(). 3. Like the API Context#createWindowContext(int windowType, Bundle options), the users of a Window Provider Service need to override provideWindowType and provideOptions to pass the attributes. 4. When there's a configuration updates from the server side, the Service#onConfigurationChanged callback will be invoked.(TBD) It is suggested to use window context when possible. This class is to migrate the Service to show UI components to the window context concept. We can't migrate them to WindowContext directly because developers are used to use this kind of Service as the container of UI components and may change its property at runtime. An example is that keyboard developers may apply a new theme by InputMethodService#getResources#setTheme(newTheme). Bug: 159767464 Test: atest WindowContextTests#testWindowProviderServiceLifecycle Change-Id: I7d537fd2d128efa28aa6e771d77aa105fb497672 --- core/api/test-current.txt | 7 + core/java/android/app/ActivityThread.java | 3 +- core/java/android/app/Service.java | 13 ++ .../window/WindowContextController.java | 1 + .../android/window/WindowProviderService.java | 138 ++++++++++++++++++ 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 core/java/android/window/WindowProviderService.java diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 63e90100b6b45..c39972760e276 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3163,5 +3163,12 @@ package android.window { method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void applyTransaction(@NonNull android.window.WindowContainerTransaction); } + @UiContext public abstract class WindowProviderService extends android.app.Service { + ctor public WindowProviderService(); + method public final void attachToWindowToken(@NonNull android.os.IBinder); + method @Nullable public android.os.Bundle getWindowContextOptions(); + method public abstract int getWindowType(); + } + } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 35890c811428b..b0235b031e031 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4385,11 +4385,12 @@ public final class ActivityThread extends ClientTransactionHandler try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); - ContextImpl context = ContextImpl.createAppContext(this, packageInfo); Application app = packageInfo.makeApplication(false, mInstrumentation); java.lang.ClassLoader cl = packageInfo.getClassLoader(); service = packageInfo.getAppFactory() .instantiateService(cl, data.info.name, data.intent); + final ContextImpl context = ContextImpl.getImpl(service + .createServiceBaseContext(this, packageInfo)); // Service resources must be initialized with the same loaders as the application // context. context.getResources().addLoaders( diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 2ceea7f1a6a8d..0ab3f2f4be463 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -860,6 +860,19 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac setContentCaptureOptions(application.getContentCaptureOptions()); } + /** + * Creates the base {@link Context} of this {@link Service}. + * Users may override this API to create customized base context. + * + * @see android.window.WindowProviderService WindowProviderService class for example + * @see ContextWrapper#attachBaseContext(Context) + * + * @hide + */ + public Context createServiceBaseContext(ActivityThread mainThread, LoadedApk packageInfo) { + return ContextImpl.createAppContext(mainThread, packageInfo); + } + /** * @hide * Clean up any references to avoid leaks. diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 88584f4b25719..d84f571931fdd 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -105,6 +105,7 @@ public class WindowContextController { * a {@link com.android.server.wm.DisplayArea} by * {@link #attachToDisplayArea(int, int, Bundle)}. * + * @see WindowProviderService#attachToWindowToken(IBinder)) * @see IWindowManager#attachWindowContextToWindowToken(IBinder, IBinder) */ public void attachToWindowToken(IBinder windowToken) { diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java new file mode 100644 index 0000000000000..b8619fbcf3348 --- /dev/null +++ b/core/java/android/window/WindowProviderService.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.TestApi; +import android.annotation.UiContext; +import android.app.ActivityThread; +import android.app.LoadedApk; +import android.app.Service; +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.Bundle; +import android.os.IBinder; +import android.view.Display; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams.WindowType; +import android.view.WindowManagerImpl; + +// TODO(b/159767464): handle #onConfigurationChanged(Configuration) +/** + * A {@link Service} responsible for showing a non-activity window, such as software keyboards or + * accessibility overlay windows. This {@link Service} has similar behavior to + * {@link WindowContext}, but is represented as {@link Service}. + * + * @see android.inputmethodservice.InputMethodService + * @see android.accessibilityservice.AccessibilityService + * + * @hide + */ +@TestApi +@UiContext +public abstract class WindowProviderService extends Service { + + private final WindowTokenClient mWindowToken = new WindowTokenClient(); + private final WindowContextController mController = new WindowContextController(mWindowToken); + private WindowManager mWindowManager; + + /** + * Returns the type of this {@link WindowProviderService}. + * Each inheriting class must implement this method to provide the type of the window. It is + * used similar to {@code type} of {@link Context#createWindowContext(int, Bundle)} + * + * @see Context#createWindowContext(int, Bundle) + * + * @hide + */ + @TestApi + @SuppressLint("OnNameExpected") + // Suppress the lint because it is not a callback and users should provide window type + // so we cannot make it final. + public abstract @WindowType int getWindowType(); + + /** + * Returns the option of this {@link WindowProviderService}. + * Default is {@code null}. The inheriting class can implement this method to provide the + * customization {@code option} of the window. It is used similar to {@code options} of + * {@link Context#createWindowContext(int, Bundle)} + * + * @see Context#createWindowContext(int, Bundle) + * + * @hide + */ + @TestApi + @SuppressLint({"OnNameExpected", "NullableCollection"}) + // Suppress the lint because it is not a callback and users may override this API to provide + // launch option. Also, the return value of this API is null by default. + @Nullable + public Bundle getWindowContextOptions() { + return null; + } + + /** + * Attaches this WindowProviderService to the {@code windowToken}. + * + * @hide + */ + @TestApi + public final void attachToWindowToken(@NonNull IBinder windowToken) { + mController.attachToWindowToken(windowToken); + } + + /** @hide */ + @Override + public final Context createServiceBaseContext(ActivityThread mainThread, + LoadedApk packageInfo) { + final Context context = super.createServiceBaseContext(mainThread, packageInfo); + // Always associate with the default display at initialization. + final Display defaultDisplay = context.getSystemService(DisplayManager.class) + .getDisplay(DEFAULT_DISPLAY); + return context.createTokenContext(mWindowToken, defaultDisplay); + } + + @CallSuper + @Override + public void onCreate() { + super.onCreate(); + mWindowToken.attachContext(this); + mController.attachToDisplayArea(getWindowType(), getDisplayId(), getWindowContextOptions()); + mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this); + } + + @SuppressLint("OnNameExpected") + @Override + // Suppress the lint because ths is overridden from Context. + public @Nullable Object getSystemService(@NonNull String name) { + if (WINDOW_SERVICE.equals(name)) { + return mWindowManager; + } + return super.getSystemService(name); + } + + @CallSuper + @Override + public void onDestroy() { + super.onDestroy(); + mController.detachIfNeeded(); + } +}