Loading core/java/android/app/IWindowToken.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -30,4 +30,6 @@ import android.view.IWindow; */ oneway interface IWindowToken { void onConfigurationChanged(in Configuration newConfig, int newDisplayId); void onWindowTokenRemoved(); } core/java/android/app/WindowContext.java +16 −3 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ import android.view.IWindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerImpl; import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.Reference; /** Loading Loading @@ -75,8 +77,6 @@ public class WindowContext extends ContextWrapper { // config back to the client. result = mWms.addWindowTokenWithOptions( mToken, type, getDisplayId(), options, getPackageName()); // TODO(window-context): remove token with a DeathObserver } catch (RemoteException e) { mOwnsToken = false; throw e.rethrowFromSystemServer(); Loading @@ -100,6 +100,13 @@ public class WindowContext extends ContextWrapper { @Override protected void finalize() throws Throwable { release(); super.finalize(); } /** Used for test to invoke because we can't invoke finalize directly. */ @VisibleForTesting public void release() { if (mOwnsToken) { try { mWms.removeWindowToken(mToken, getDisplayId()); Loading @@ -108,6 +115,12 @@ public class WindowContext extends ContextWrapper { throw e.rethrowFromSystemServer(); } } super.finalize(); destroy(); } void destroy() { final ContextImpl impl = (ContextImpl) getBaseContext(); impl.scheduleFinalCleanup(getClass().getName(), "WindowContext"); Reference.reachabilityFence(this); } } core/java/android/app/WindowTokenClient.java +29 −10 Original line number Diff line number Diff line Loading @@ -20,6 +20,9 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.view.WindowManagerGlobal; import java.lang.ref.WeakReference; /** * Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from Loading @@ -31,9 +34,9 @@ import android.os.IBinder; public class WindowTokenClient extends IWindowToken.Stub { /** * Attached {@link Context} for this window token to update configuration and resources. * Initialized by {@link #attachContext(Context)}. * Initialized by {@link #attachContext(WindowContext)}. */ private Context mContext = null; private WeakReference<WindowContext> mContextRef = null; private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); Loading @@ -47,30 +50,46 @@ public class WindowTokenClient extends IWindowToken.Stub { * @param context context to be attached * @throws IllegalStateException if attached context has already existed. */ void attachContext(@NonNull Context context) { if (mContext != null) { void attachContext(@NonNull WindowContext context) { if (mContextRef != null) { throw new IllegalStateException("Context is already attached."); } mContext = context; ContextImpl impl = ContextImpl.getImpl(mContext); mContextRef = new WeakReference<>(context); final ContextImpl impl = ContextImpl.getImpl(context); impl.setResources(impl.createWindowContextResources()); } @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { final int currentDisplayId = mContext.getDisplayId(); final Context context = mContextRef.get(); if (context == null) { return; } final int currentDisplayId = context.getDisplayId(); final boolean displayChanged = newDisplayId != currentDisplayId; final Configuration config = new Configuration(mContext.getResources() final Configuration config = new Configuration(context.getResources() .getConfiguration()); final boolean configChanged = config.isOtherSeqNewer(newConfig) && config.updateFrom(newConfig) != 0; if (displayChanged || configChanged) { // TODO(ag/9789103): update resource manager logic to track non-activity tokens mResourcesManager.updateResourcesForActivity(asBinder(), config, newDisplayId, mResourcesManager.updateResourcesForActivity(this, config, newDisplayId, displayChanged); } if (displayChanged) { mContext.updateDisplay(newDisplayId); context.updateDisplay(newDisplayId); } } @Override public void onWindowTokenRemoved() { final WindowContext context = mContextRef.get(); if (context != null) { context.destroy(); mContextRef.clear(); } // If a secondary display is detached, release all views attached to this token. WindowManagerGlobal.getInstance().closeAll(this, mContextRef.getClass().getName(), "WindowContext"); } } core/java/android/view/IWindowManager.aidl +8 −1 Original line number Diff line number Diff line Loading @@ -124,13 +124,20 @@ interface IWindowManager * @param type Window type to be used with this token. * @param options A bundle used to pass window-related options. * @param displayId The ID of the display where this token should be added. * @param packageName The name of package to request to add window token. * @param packageName The name of package to request to add window token. Could be {@code null} * if callers holds the MANAGE_APP_TOKENS permission. * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code * otherwise. */ int addWindowTokenWithOptions(IBinder token, int type, int displayId, in Bundle options, String packageName); void addWindowToken(IBinder token, int type, int displayId); /** * Remove window token on a specific display. * * @param token Token to be removed * @displayId The ID of the display where this token should be removed. */ void removeWindowToken(IBinder token, int displayId); void prepareAppTransition(int transit, boolean alwaysKeepCurrent); Loading core/tests/coretests/src/android/app/WindowContextTest.java 0 → 100644 +74 −0 Original line number Diff line number Diff line /* * 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 android.app; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.IWindowManager; import android.view.WindowManagerGlobal; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests for {@link WindowContext} * * <p>Build/Install/Run: * atest FrameworksCoreTests:WindowContextTest * * <p>This test class is a part of Window Manager Service tests and specified in * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. */ @FlakyTest(bugId = 150812449, detail = "Remove after confirmed it's stable.") @RunWith(AndroidJUnit4.class) @SmallTest @Presubmit public class WindowContextTest { @Test public void testWindowContextRelease_doRemoveWindowToken() throws Throwable { final Context instContext = InstrumentationRegistry.getInstrumentation() .getTargetContext(); final Display display = instContext.getSystemService(DisplayManager.class) .getDisplay(DEFAULT_DISPLAY); final Context context = instContext.createDisplayContext(display); final WindowContext windowContext = new WindowContext(context, TYPE_APPLICATION_OVERLAY, null /* options */); final IBinder token = windowContext.getActivityToken(); final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); assertTrue("Token must be registered to WMS", wms.isWindowToken(token)); windowContext.release(); assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token)); } } Loading
core/java/android/app/IWindowToken.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -30,4 +30,6 @@ import android.view.IWindow; */ oneway interface IWindowToken { void onConfigurationChanged(in Configuration newConfig, int newDisplayId); void onWindowTokenRemoved(); }
core/java/android/app/WindowContext.java +16 −3 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ import android.view.IWindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerImpl; import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.Reference; /** Loading Loading @@ -75,8 +77,6 @@ public class WindowContext extends ContextWrapper { // config back to the client. result = mWms.addWindowTokenWithOptions( mToken, type, getDisplayId(), options, getPackageName()); // TODO(window-context): remove token with a DeathObserver } catch (RemoteException e) { mOwnsToken = false; throw e.rethrowFromSystemServer(); Loading @@ -100,6 +100,13 @@ public class WindowContext extends ContextWrapper { @Override protected void finalize() throws Throwable { release(); super.finalize(); } /** Used for test to invoke because we can't invoke finalize directly. */ @VisibleForTesting public void release() { if (mOwnsToken) { try { mWms.removeWindowToken(mToken, getDisplayId()); Loading @@ -108,6 +115,12 @@ public class WindowContext extends ContextWrapper { throw e.rethrowFromSystemServer(); } } super.finalize(); destroy(); } void destroy() { final ContextImpl impl = (ContextImpl) getBaseContext(); impl.scheduleFinalCleanup(getClass().getName(), "WindowContext"); Reference.reachabilityFence(this); } }
core/java/android/app/WindowTokenClient.java +29 −10 Original line number Diff line number Diff line Loading @@ -20,6 +20,9 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.view.WindowManagerGlobal; import java.lang.ref.WeakReference; /** * Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from Loading @@ -31,9 +34,9 @@ import android.os.IBinder; public class WindowTokenClient extends IWindowToken.Stub { /** * Attached {@link Context} for this window token to update configuration and resources. * Initialized by {@link #attachContext(Context)}. * Initialized by {@link #attachContext(WindowContext)}. */ private Context mContext = null; private WeakReference<WindowContext> mContextRef = null; private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); Loading @@ -47,30 +50,46 @@ public class WindowTokenClient extends IWindowToken.Stub { * @param context context to be attached * @throws IllegalStateException if attached context has already existed. */ void attachContext(@NonNull Context context) { if (mContext != null) { void attachContext(@NonNull WindowContext context) { if (mContextRef != null) { throw new IllegalStateException("Context is already attached."); } mContext = context; ContextImpl impl = ContextImpl.getImpl(mContext); mContextRef = new WeakReference<>(context); final ContextImpl impl = ContextImpl.getImpl(context); impl.setResources(impl.createWindowContextResources()); } @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { final int currentDisplayId = mContext.getDisplayId(); final Context context = mContextRef.get(); if (context == null) { return; } final int currentDisplayId = context.getDisplayId(); final boolean displayChanged = newDisplayId != currentDisplayId; final Configuration config = new Configuration(mContext.getResources() final Configuration config = new Configuration(context.getResources() .getConfiguration()); final boolean configChanged = config.isOtherSeqNewer(newConfig) && config.updateFrom(newConfig) != 0; if (displayChanged || configChanged) { // TODO(ag/9789103): update resource manager logic to track non-activity tokens mResourcesManager.updateResourcesForActivity(asBinder(), config, newDisplayId, mResourcesManager.updateResourcesForActivity(this, config, newDisplayId, displayChanged); } if (displayChanged) { mContext.updateDisplay(newDisplayId); context.updateDisplay(newDisplayId); } } @Override public void onWindowTokenRemoved() { final WindowContext context = mContextRef.get(); if (context != null) { context.destroy(); mContextRef.clear(); } // If a secondary display is detached, release all views attached to this token. WindowManagerGlobal.getInstance().closeAll(this, mContextRef.getClass().getName(), "WindowContext"); } }
core/java/android/view/IWindowManager.aidl +8 −1 Original line number Diff line number Diff line Loading @@ -124,13 +124,20 @@ interface IWindowManager * @param type Window type to be used with this token. * @param options A bundle used to pass window-related options. * @param displayId The ID of the display where this token should be added. * @param packageName The name of package to request to add window token. * @param packageName The name of package to request to add window token. Could be {@code null} * if callers holds the MANAGE_APP_TOKENS permission. * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code * otherwise. */ int addWindowTokenWithOptions(IBinder token, int type, int displayId, in Bundle options, String packageName); void addWindowToken(IBinder token, int type, int displayId); /** * Remove window token on a specific display. * * @param token Token to be removed * @displayId The ID of the display where this token should be removed. */ void removeWindowToken(IBinder token, int displayId); void prepareAppTransition(int transit, boolean alwaysKeepCurrent); Loading
core/tests/coretests/src/android/app/WindowContextTest.java 0 → 100644 +74 −0 Original line number Diff line number Diff line /* * 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 android.app; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.IWindowManager; import android.view.WindowManagerGlobal; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests for {@link WindowContext} * * <p>Build/Install/Run: * atest FrameworksCoreTests:WindowContextTest * * <p>This test class is a part of Window Manager Service tests and specified in * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. */ @FlakyTest(bugId = 150812449, detail = "Remove after confirmed it's stable.") @RunWith(AndroidJUnit4.class) @SmallTest @Presubmit public class WindowContextTest { @Test public void testWindowContextRelease_doRemoveWindowToken() throws Throwable { final Context instContext = InstrumentationRegistry.getInstrumentation() .getTargetContext(); final Display display = instContext.getSystemService(DisplayManager.class) .getDisplay(DEFAULT_DISPLAY); final Context context = instContext.createDisplayContext(display); final WindowContext windowContext = new WindowContext(context, TYPE_APPLICATION_OVERLAY, null /* options */); final IBinder token = windowContext.getActivityToken(); final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); assertTrue("Token must be registered to WMS", wms.isWindowToken(token)); windowContext.release(); assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token)); } }