diff options
51 files changed, 542 insertions, 1149 deletions
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 0714285c43f6..d8a88b83df99 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -138,12 +138,6 @@ public class FeatureFlagUtils { "settings_show_stylus_preferences"; /** - * Flag to enable/disable biometrics enrollment v2 - * @hide - */ - public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment"; - - /** * Flag to enable/disable FingerprintSettings v2 * @hide */ @@ -223,7 +217,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "true"); DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true"); - DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false"); DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false"); DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true"); diff --git a/core/java/android/window/ActivityWindowInfo.java b/core/java/android/window/ActivityWindowInfo.java index 946bb823398c..71c500cd1bfa 100644 --- a/core/java/android/window/ActivityWindowInfo.java +++ b/core/java/android/window/ActivityWindowInfo.java @@ -18,6 +18,8 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Activity; +import android.app.ActivityThread; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; @@ -144,4 +146,15 @@ public final class ActivityWindowInfo implements Parcelable { + ", taskFragmentBounds=" + mTaskFragmentBounds + "}"; } + + /** Gets the {@link ActivityWindowInfo} of the given activity. */ + @Nullable + public static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) { + if (activity.isFinishing()) { + return null; + } + final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread() + .getActivityClient(activity.getActivityToken()); + return record != null ? record.getActivityWindowInfo() : null; + } } diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 2c64b8ed456d..ac57c0056c2b 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -39,12 +39,6 @@ interface ITaskFragmentOrganizerController { void unregisterOrganizer(in ITaskFragmentOrganizer organizer); /** - * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and - * only occupies a portion of Task bounds. - */ - boolean isActivityEmbedded(in IBinder activityToken); - - /** * Notifies the server that the organizer has finished handling the given transaction. The * server should apply the given {@link WindowContainerTransaction} for the necessary changes. */ diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 15f1258ccd69..8e429cb376a6 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.ActivityWindowInfo.getActivityWindowInfo; import android.annotation.CallSuper; import android.annotation.FlaggedApi; @@ -29,6 +30,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.TestApi; +import android.app.Activity; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -38,6 +40,7 @@ import com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -324,16 +327,15 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** - * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * Checks if an activity is organized by a {@link android.window.TaskFragmentOrganizer} and * only occupies a portion of Task bounds. + * + * @see ActivityWindowInfo for additional window info. * @hide */ - // TODO(b/287582673): cleanup - public boolean isActivityEmbedded(@NonNull IBinder activityToken) { - try { - return getController().isActivityEmbedded(activityToken); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + public static boolean isActivityEmbedded(@NonNull Activity activity) { + Objects.requireNonNull(activity); + final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity); + return activityWindowInfo != null && activityWindowInfo.isEmbedded(); } } diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index cc880e182b2b..48fb2b3ab129 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -91,6 +91,13 @@ flag { } flag { + name: "camera_compat_fullscreen_pick_same_task_activity" + namespace: "large_screen_experiences_app_compat" + description: "Limit undo of camera compat treatment to the same task that started the treatment." + bug: "350495350" +} + +flag { name: "app_compat_refactoring" namespace: "large_screen_experiences_app_compat" description: "Whether the changes about app compat refactoring are enabled./n" diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index b12072373c5d..8e1fde066277 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.window.ActivityWindowInfo.getActivityWindowInfo; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; @@ -80,6 +81,7 @@ import android.window.ActivityWindowInfo; import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOperation; +import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -2553,9 +2555,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return ActivityThread.currentActivityThread().getActivity(activityToken); } - @VisibleForTesting @Nullable - ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) { + private ActivityThread.ActivityClientRecord getActivityClientRecord( + @NonNull Activity activity) { return ActivityThread.currentActivityThread() .getActivityClient(activity.getActivityToken()); } @@ -3092,10 +3094,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @Override public boolean isActivityEmbedded(@NonNull Activity activity) { - Objects.requireNonNull(activity); synchronized (mLock) { - final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity); - return activityWindowInfo != null && activityWindowInfo.isEmbedded(); + return TaskFragmentOrganizer.isActivityEmbedded(activity); } } @@ -3165,15 +3165,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - @Nullable - private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) { - if (activity.isFinishing()) { - return null; - } - final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity); - return record != null ? record.getActivityWindowInfo() : null; - } - @NonNull private static EmbeddedActivityWindowInfo translateActivityWindowInfo( @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 99c0ee29962c..d852204b88a8 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -165,6 +165,7 @@ public class SplitControllerTest { private Consumer<List<SplitInfo>> mEmbeddingCallback; private List<SplitInfo> mSplitInfos; private TransactionManager mTransactionManager; + private ActivityThread mCurrentActivityThread; @Before public void setUp() { @@ -181,10 +182,12 @@ public class SplitControllerTest { }; mSplitController.setSplitInfoCallback(mEmbeddingCallback); mTransactionManager = mSplitController.mTransactionManager; + mCurrentActivityThread = ActivityThread.currentActivityThread(); spyOn(mSplitController); spyOn(mSplitPresenter); spyOn(mEmbeddingCallback); spyOn(mTransactionManager); + spyOn(mCurrentActivityThread); doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean()); final Configuration activityConfig = new Configuration(); activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); @@ -1668,7 +1671,8 @@ public class SplitControllerTest { final IBinder activityToken = new Binder(); doReturn(activityToken).when(activity).getActivityToken(); doReturn(activity).when(mSplitController).getActivity(activityToken); - doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity); + doReturn(activityClientRecord).when(mCurrentActivityThread).getActivityClient( + activityToken); doReturn(taskId).when(activity).getTaskId(); doReturn(new ActivityInfo()).when(activity).getActivityInfo(); doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index df5644b8aad0..26453609fc43 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -46,20 +46,6 @@ </intent-filter> </provider> - <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider" - android:authorities="com.android.spa.gallery.slice.provider" - android:exported="true" > - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.app.slice.category.SLICE" /> - </intent-filter> - </provider> - - <receiver - android:name="com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver" - android:exported="false"> - </receiver> - <activity android:name="com.android.settingslib.spa.debug.BlankActivity" android:exported="true"> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 91bd7916b0ab..ffd28798d82f 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -55,7 +55,6 @@ import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.scaffold.ScrollablePagerPageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider -import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver /** * Enum to define all SPP name here. @@ -120,9 +119,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { override val logger = DebugLogger() override val browseActivityClass = GalleryMainActivity::class.java - override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java // For debugging override val searchProviderAuthorities = "com.android.spa.gallery.search.provider" - override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider" } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt index 96de1a778c97..6d1d34628efa 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.common.EntrySearchData -import com.android.settingslib.spa.framework.common.EntrySliceData import com.android.settingslib.spa.framework.common.EntryStatusData import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsEntryBuilder @@ -35,10 +34,8 @@ import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.framework.util.createIntent import com.android.settingslib.spa.gallery.R import com.android.settingslib.spa.gallery.SettingsPageProviderEnum -import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY @@ -48,15 +45,10 @@ import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Compan import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE -import com.android.settingslib.spa.slice.createBrowsePendingIntent -import com.android.settingslib.spa.slice.provider.createDemoActionSlice -import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice -import com.android.settingslib.spa.slice.provider.createDemoSlice import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro import com.android.settingslib.spa.widget.ui.SettingsIcon -import kotlinx.coroutines.delay private const val TAG = "PreferencePage" @@ -139,26 +131,6 @@ object PreferencePageProvider : SettingsPageProvider { override val enabled = { model.asyncEnable.value } } ) - } - .setSliceDataFn { sliceUri, _ -> - val createSliceImpl = { s: String -> - createDemoBrowseSlice( - sliceUri = sliceUri, - title = ASYNC_PREFERENCE_TITLE, - summary = s, - ) - } - return@setSliceDataFn object : EntrySliceData() { - init { - postValue(createSliceImpl("(loading)")) - } - - override suspend fun asyncRunner() { - spaLogger.message(TAG, "Async entry loading") - delay(2000L) - postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY)) - } - } }.build() ) entryList.add( @@ -176,28 +148,6 @@ object PreferencePageProvider : SettingsPageProvider { } } ) - } - .setSliceDataFn { sliceUri, args -> - val createSliceImpl = { v: Int -> - createDemoActionSlice( - sliceUri = sliceUri, - title = MANUAL_UPDATE_PREFERENCE_TITLE, - summary = "manual update value $v", - ) - } - - return@setSliceDataFn object : EntrySliceData() { - private var tick = args?.getString("init")?.toInt() ?: 0 - - init { - postValue(createSliceImpl(tick)) - } - - override suspend fun asyncAction() { - tick++ - postValue(createSliceImpl(tick)) - } - } }.build() ) entryList.add( @@ -216,33 +166,6 @@ object PreferencePageProvider : SettingsPageProvider { } } ) - } - .setSliceDataFn { sliceUri, args -> - val createSliceImpl = { v: Int -> - createDemoBrowseSlice( - sliceUri = sliceUri, - title = AUTO_UPDATE_PREFERENCE_TITLE, - summary = "auto update value $v", - ) - } - - return@setSliceDataFn object : EntrySliceData() { - private var tick = args?.getString("init")?.toInt() ?: 0 - - init { - postValue(createSliceImpl(tick)) - } - - override suspend fun asyncRunner() { - spaLogger.message(TAG, "autoUpdater.active") - while (true) { - delay(1000L) - tick++ - spaLogger.message(TAG, "autoUpdater.value $tick") - postValue(createSliceImpl(tick)) - } - } - } }.build() ) @@ -272,22 +195,6 @@ object PreferencePageProvider : SettingsPageProvider { clickRoute = SettingsPageProviderEnum.PREFERENCE.name ) } - .setSliceDataFn { sliceUri, _ -> - val intent = owner.createIntent()?.createBrowsePendingIntent() - ?: return@setSliceDataFn null - return@setSliceDataFn object : EntrySliceData() { - init { - postValue( - createDemoSlice( - sliceUri = sliceUri, - title = PAGE_TITLE, - summary = "Injected Entry", - intent = intent, - ) - ) - } - } - } } override fun getTitle(arguments: Bundle?): String { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt index 2d956d5eddb2..6e5132bbb53e 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt @@ -17,12 +17,10 @@ package com.android.settingslib.spa.framework.common import android.app.Activity -import android.content.BroadcastReceiver import android.content.Context import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext -import com.android.settingslib.spa.slice.SettingsSliceDataRepository private const val TAG = "SpaEnvironment" @@ -69,8 +67,6 @@ abstract class SpaEnvironment(context: Context) { val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) } - val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) } - // The application context. Use local context as fallback when applicationContext is not // available (e.g. in Robolectric test). val appContext: Context = context.applicationContext ?: context @@ -81,11 +77,9 @@ abstract class SpaEnvironment(context: Context) { // Specify class name of browse activity and slice broadcast receiver, which is used to // generate the necessary intents. open val browseActivityClass: Class<out Activity>? = null - open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null // Specify provider authorities for debugging purpose. open val searchProviderAuthorities: String? = null - open val sliceProviderAuthorities: String? = null // TODO: add other environment setup here. companion object { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt deleted file mode 100644 index 7a4750dfb134..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt +++ /dev/null @@ -1,57 +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.settingslib.spa.slice - -import android.net.Uri -import android.util.Log -import com.android.settingslib.spa.framework.common.EntrySliceData -import com.android.settingslib.spa.framework.common.SettingsEntryRepository -import com.android.settingslib.spa.framework.util.getEntryId - -private const val TAG = "SliceDataRepository" - -class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) { - // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?> - private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf() - - // Note: mark this function synchronized, so that we can get the same livedata during the - // whole lifecycle of a Slice. - @Synchronized - fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? { - val sliceString = sliceUri.getSliceId() ?: return null - return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let { - sliceDataMap[sliceString] = it - it - } - } - - fun getActiveSliceData(sliceUri: Uri): EntrySliceData? { - val sliceString = sliceUri.getSliceId() ?: return null - val sliceData = sliceDataMap[sliceString] ?: return null - return if (sliceData.isActive()) sliceData else null - } - - private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? { - Log.d(TAG, "buildLiveData: $sliceUri") - - val entryId = sliceUri.getEntryId() ?: return null - val entry = entryRepository.getEntry(entryId) ?: return null - if (!entry.hasSliceSupport) return null - val arguments = sliceUri.getRuntimeArguments() - return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri) - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt index f3628903dc6d..ec89c7cd3a6c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt @@ -16,23 +16,10 @@ package com.android.settingslib.spa.slice -import android.app.Activity -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.ComponentName -import android.content.Context -import android.content.Intent import android.net.Uri import android.os.Bundle -import com.android.settingslib.spa.framework.common.SettingsEntry -import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.util.KEY_DESTINATION import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY -import com.android.settingslib.spa.framework.util.SESSION_SLICE -import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS -import com.android.settingslib.spa.framework.util.appendSpaParams -import com.android.settingslib.spa.framework.util.getDestination -import com.android.settingslib.spa.framework.util.getEntryId // Defines SliceUri, which contains special query parameters: // -- KEY_DESTINATION: The route that this slice is navigated to. @@ -45,25 +32,6 @@ fun SliceUri.getEntryId(): String? { return getQueryParameter(KEY_HIGHLIGHT_ENTRY) } -fun SliceUri.getDestination(): String? { - return getQueryParameter(KEY_DESTINATION) -} - -fun SliceUri.getRuntimeArguments(): Bundle { - val params = Bundle() - for (queryName in queryParameterNames) { - if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue - params.putString(queryName, getQueryParameter(queryName)) - } - return params -} - -fun SliceUri.getSliceId(): String? { - val entryId = getEntryId() ?: return null - val params = getRuntimeArguments() - return "${entryId}_$params" -} - fun Uri.Builder.appendSpaParams( destination: String? = null, entryId: String? = null, @@ -79,72 +47,3 @@ fun Uri.Builder.appendSpaParams( return this } -fun Uri.Builder.fromEntry( - entry: SettingsEntry, - authority: String?, - runtimeArguments: Bundle? = null -): Uri.Builder { - if (authority == null) return this - val sp = entry.containerPage() - return scheme("content").authority(authority).appendSpaParams( - destination = sp.buildRoute(), - entryId = entry.id, - runtimeArguments = runtimeArguments - ) -} - -fun SliceUri.createBroadcastPendingIntent(): PendingIntent? { - val context = SpaEnvironmentFactory.instance.appContext - val sliceBroadcastClass = - SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null - val entryId = getEntryId() ?: return null - return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId) -} - -fun SliceUri.createBrowsePendingIntent(): PendingIntent? { - val context = SpaEnvironmentFactory.instance.appContext - val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null - val destination = getDestination() ?: return null - val entryId = getEntryId() - return createBrowsePendingIntent(context, browseActivityClass, destination, entryId) -} - -fun Intent.createBrowsePendingIntent(): PendingIntent? { - val context = SpaEnvironmentFactory.instance.appContext - val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null - val destination = getDestination() ?: return null - val entryId = getEntryId() - return createBrowsePendingIntent(context, browseActivityClass, destination, entryId) -} - -private fun createBrowsePendingIntent( - context: Context, - browseActivityClass: Class<out Activity>, - destination: String, - entryId: String? -): PendingIntent { - val intent = Intent().setComponent(ComponentName(context, browseActivityClass)) - .appendSpaParams(destination, entryId, SESSION_SLICE) - .apply { - // Set both extra and data (which is a Uri) in Slice Intent: - // 1) extra is used in SPA navigation framework - // 2) data is used in Slice framework - data = Uri.Builder().appendSpaParams(destination, entryId).build() - flags = Intent.FLAG_ACTIVITY_NEW_TASK - } - - return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) -} - -private fun createBroadcastPendingIntent( - context: Context, - sliceBroadcastClass: Class<out BroadcastReceiver>, - entryId: String -): PendingIntent { - val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass)) - .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() } - return PendingIntent.getBroadcast( - context, 0 /* requestCode */, intent, - PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE - ) -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt deleted file mode 100644 index 39cb43180f58..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt +++ /dev/null @@ -1,31 +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.settingslib.spa.slice - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory - -class SpaSliceBroadcastReceiver : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository - val sliceUri = intent?.data ?: return - val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return - sliceData.doAction() - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt deleted file mode 100644 index 3496f02a70e4..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt +++ /dev/null @@ -1,77 +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.settingslib.spa.slice - -import android.net.Uri -import android.util.Log -import androidx.lifecycle.Observer -import androidx.slice.Slice -import androidx.slice.SliceProvider -import com.android.settingslib.spa.framework.common.EntrySliceData -import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext - -private const val TAG = "SpaSliceProvider" - -class SpaSliceProvider : SliceProvider(), Observer<Slice?> { - private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? { - if (!SpaEnvironmentFactory.isReady()) return null - val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository - return sliceRepository.getOrBuildSliceData(sliceUri) - } - - override fun onBindSlice(sliceUri: Uri): Slice? { - if (context == null) return null - Log.d(TAG, "onBindSlice: $sliceUri") - return getOrPutSliceData(sliceUri)?.value - } - - override fun onSlicePinned(sliceUri: Uri) { - Log.d(TAG, "onSlicePinned: $sliceUri") - super.onSlicePinned(sliceUri) - val sliceLiveData = getOrPutSliceData(sliceUri) ?: return - runBlocking { - withContext(Dispatchers.Main) { - sliceLiveData.observeForever(this@SpaSliceProvider) - } - } - } - - override fun onSliceUnpinned(sliceUri: Uri) { - Log.d(TAG, "onSliceUnpinned: $sliceUri") - super.onSliceUnpinned(sliceUri) - val sliceLiveData = getOrPutSliceData(sliceUri) ?: return - runBlocking { - withContext(Dispatchers.Main) { - sliceLiveData.removeObserver(this@SpaSliceProvider) - } - } - } - - override fun onChanged(value: Slice?) { - val uri = value?.uri ?: return - Log.d(TAG, "onChanged: $uri") - context?.contentResolver?.notifyChange(uri, null) - } - - override fun onCreateSliceProvider(): Boolean { - Log.d(TAG, "onCreateSliceProvider") - return true - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt deleted file mode 100644 index 007f47bd3c82..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt +++ /dev/null @@ -1,47 +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.settingslib.spa.slice.presenter - -import android.net.Uri -import androidx.compose.material3.HorizontalDivider -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.viewinterop.AndroidView -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.slice.widget.SliceLiveData -import androidx.slice.widget.SliceView - -@Composable -fun SliceDemo(sliceUri: Uri) { - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - val sliceData = remember { - SliceLiveData.fromUri(context, sliceUri) - } - - HorizontalDivider() - AndroidView( - factory = { localContext -> - val view = SliceView(localContext) - view.setShowTitleItems(true) - view.isScrollable = false - view - }, - update = { view -> sliceData.observe(lifecycleOwner, view) } - ) -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt deleted file mode 100644 index e4a738631474..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt +++ /dev/null @@ -1,60 +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.settingslib.spa.slice.provider - -import android.app.PendingIntent -import android.content.Context -import android.net.Uri -import androidx.core.R -import androidx.core.graphics.drawable.IconCompat -import androidx.slice.Slice -import androidx.slice.SliceManager -import androidx.slice.builders.ListBuilder -import androidx.slice.builders.SliceAction -import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory -import com.android.settingslib.spa.slice.createBroadcastPendingIntent -import com.android.settingslib.spa.slice.createBrowsePendingIntent - -fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? { - val intent = sliceUri.createBrowsePendingIntent() ?: return null - return createDemoSlice(sliceUri, title, summary, intent) -} - -fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? { - val intent = sliceUri.createBroadcastPendingIntent() ?: return null - return createDemoSlice(sliceUri, title, summary, intent) -} - -fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? { - val context = SpaEnvironmentFactory.instance.appContext - if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null - return ListBuilder(context, sliceUri, ListBuilder.INFINITY) - .addRow(ListBuilder.RowBuilder().apply { - setPrimaryAction(createSliceAction(context, intent)) - setTitle(title) - setSubtitle(summary) - }).build() -} - -private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction { - return SliceAction.create( - intent, - IconCompat.createWithResource(context, R.drawable.notification_action_background), - ListBuilder.ICON_IMAGE, - "Enter app" - ) -} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt deleted file mode 100644 index 341a4a5134f9..000000000000 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt +++ /dev/null @@ -1,97 +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.settingslib.spa.slice - -import android.content.Context -import android.net.Uri -import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Observer -import androidx.slice.Slice -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory -import com.android.settingslib.spa.framework.common.createSettingsPage -import com.android.settingslib.spa.framework.util.genEntryId -import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest -import com.android.settingslib.spa.tests.testutils.SppHome -import com.android.settingslib.spa.tests.testutils.SppLayer2 -import com.google.common.truth.Truth.assertThat -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class SettingsSliceDataRepositoryTest { - @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() - - private val context: Context = ApplicationProvider.getApplicationContext() - private val spaEnvironment = - SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage())) - private val sliceDataRepository by spaEnvironment.sliceDataRepository - - @Test - fun getOrBuildSliceDataTest() { - SpaEnvironmentFactory.reset(spaEnvironment) - - // Slice empty - assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull() - - // Slice supported - val page = SppLayer2.createSettingsPage() - val entryId = genEntryId("Layer2Entry1", page) - val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build() - assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2") - assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]") - val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri) - assertThat(sliceData).isNotNull() - assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData) - - // Slice unsupported - val entryId2 = genEntryId("Layer2Entry2", page) - val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build() - assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2") - assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]") - assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull() - } - - @Test - fun getActiveSliceDataTest() { - SpaEnvironmentFactory.reset(spaEnvironment) - - val page = SppLayer2.createSettingsPage() - val entryId = genEntryId("Layer2Entry1", page) - val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build() - - // build slice data first - val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri) - - // slice data is inactive - assertThat(sliceData!!.isActive()).isFalse() - assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull() - - // slice data is active - val observer = Observer<Slice?> { } - sliceData.observeForever(observer) - assertThat(sliceData.isActive()).isTrue() - assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData) - - // slice data is inactive again - sliceData.removeObserver(observer) - assertThat(sliceData.isActive()).isFalse() - assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull() - } -} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt index d1c4e5110f60..b489afdae157 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt @@ -16,91 +16,27 @@ package com.android.settingslib.spa.slice -import android.content.Context -import android.content.Intent import android.net.Uri import androidx.core.os.bundleOf -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory -import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SliceUtilTest { - private val context: Context = ApplicationProvider.getApplicationContext() - private val spaEnvironment = SpaEnvironmentForTest(context) - @Test fun sliceUriTest() { assertThat(Uri.EMPTY.getEntryId()).isNull() - assertThat(Uri.EMPTY.getDestination()).isNull() - assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0) - assertThat(Uri.EMPTY.getSliceId()).isNull() // valid slice uri val dest = "myRoute" val entryId = "myEntry" val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build() assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId) - assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest) - assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0) - assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]") val sliceUriWithParams = Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build() assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId) - assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest) - assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1) - assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]") - } - - @Test - fun createBroadcastPendingIntentTest() { - SpaEnvironmentFactory.reset(spaEnvironment) - - // Empty Slice Uri - assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull() - - // Valid Slice Uri - val dest = "myRoute" - val entryId = "myEntry" - val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build() - val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent() - assertThat(pendingIntent).isNotNull() - assertThat(pendingIntent!!.isBroadcast).isTrue() - assertThat(pendingIntent.isImmutable).isFalse() - } - - @Test - fun createBrowsePendingIntentTest() { - SpaEnvironmentFactory.reset(spaEnvironment) - - // Empty Slice Uri - assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull() - - // Empty Intent - assertThat(Intent().createBrowsePendingIntent()).isNull() - - // Valid Slice Uri - val dest = "myRoute" - val entryId = "myEntry" - val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build() - val pendingIntent = sliceUri.createBrowsePendingIntent() - assertThat(pendingIntent).isNotNull() - assertThat(pendingIntent!!.isActivity).isTrue() - assertThat(pendingIntent.isImmutable).isTrue() - - // Valid Intent - val intent = Intent().apply { - putExtra("spaActivityDestination", dest) - putExtra("highlightEntry", entryId) - } - val pendingIntent2 = intent.createBrowsePendingIntent() - assertThat(pendingIntent2).isNotNull() - assertThat(pendingIntent2!!.isActivity).isTrue() - assertThat(pendingIntent2.isImmutable).isTrue() } }
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt index 22a5ca328755..4f8fd794b248 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt @@ -216,8 +216,6 @@ class SpaEnvironmentForTest( context: Context, rootPages: List<SettingsPage> = emptyList(), override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java, - override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = - BlankSliceBroadcastReceiver::class.java, override val logger: SpaLogger = object : SpaLogger {} ) : SpaEnvironment(context) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index d95b388eb347..20b1303ae6bd 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -184,31 +184,33 @@ internal class DraggableHandlerImpl( ): Swipes { val fromSource = startedPosition?.let { position -> - layoutImpl.swipeSourceDetector.source( - fromScene.targetSize, - position.round(), - layoutImpl.density, - orientation, - ) + layoutImpl.swipeSourceDetector + .source( + fromScene.targetSize, + position.round(), + layoutImpl.density, + orientation, + ) + ?.resolve(layoutImpl.layoutDirection) } val upOrLeft = - Swipe( + Swipe.Resolved( direction = when (orientation) { - Orientation.Horizontal -> SwipeDirection.Left - Orientation.Vertical -> SwipeDirection.Up + Orientation.Horizontal -> SwipeDirection.Resolved.Left + Orientation.Vertical -> SwipeDirection.Resolved.Up }, pointerCount = pointersDown, fromSource = fromSource, ) val downOrRight = - Swipe( + Swipe.Resolved( direction = when (orientation) { - Orientation.Horizontal -> SwipeDirection.Right - Orientation.Vertical -> SwipeDirection.Down + Orientation.Horizontal -> SwipeDirection.Resolved.Right + Orientation.Vertical -> SwipeDirection.Resolved.Down }, pointerCount = pointersDown, fromSource = fromSource, @@ -833,10 +835,10 @@ private object DefaultSwipeDistance : UserActionDistance { /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */ private class Swipes( - val upOrLeft: Swipe?, - val downOrRight: Swipe?, - val upOrLeftNoSource: Swipe?, - val downOrRightNoSource: Swipe?, + val upOrLeft: Swipe.Resolved?, + val downOrRight: Swipe.Resolved?, + val upOrLeftNoSource: Swipe.Resolved?, + val downOrRightNoSource: Swipe.Resolved?, ) { /** The [UserActionResult] associated to up and down swipes. */ var upOrLeftResult: UserActionResult? = null @@ -844,7 +846,7 @@ private class Swipes( fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> { val userActions = fromScene.userActions - fun result(swipe: Swipe?): UserActionResult? { + fun result(swipe: Swipe.Resolved?): UserActionResult? { return userActions[swipe ?: return null] } @@ -940,25 +942,27 @@ internal class NestedScrollHandlerImpl( when { amount < 0f -> { val actionUpOrLeft = - Swipe( + Swipe.Resolved( direction = when (orientation) { - Orientation.Horizontal -> SwipeDirection.Left - Orientation.Vertical -> SwipeDirection.Up + Orientation.Horizontal -> SwipeDirection.Resolved.Left + Orientation.Vertical -> SwipeDirection.Resolved.Up }, pointerCount = pointersInfo().pointersDown, + fromSource = null, ) fromScene.userActions[actionUpOrLeft] } amount > 0f -> { val actionDownOrRight = - Swipe( + Swipe.Resolved( direction = when (orientation) { - Orientation.Horizontal -> SwipeDirection.Right - Orientation.Vertical -> SwipeDirection.Down + Orientation.Horizontal -> SwipeDirection.Resolved.Right + Orientation.Vertical -> SwipeDirection.Resolved.Down }, pointerCount = pointersInfo().pointersDown, + fromSource = null, ) fromScene.userActions[actionDownOrRight] } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt index b0dc3a144533..97c0cef30388 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt @@ -21,14 +21,28 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp /** The edge of a [SceneTransitionLayout]. */ -enum class Edge : SwipeSource { - Left, - Right, - Top, - Bottom, +enum class Edge(private val resolveEdge: (LayoutDirection) -> Resolved) : SwipeSource { + Top(resolveEdge = { Resolved.Top }), + Bottom(resolveEdge = { Resolved.Bottom }), + Left(resolveEdge = { Resolved.Left }), + Right(resolveEdge = { Resolved.Right }), + Start(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }), + End(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left }); + + override fun resolve(layoutDirection: LayoutDirection): Resolved { + return resolveEdge(layoutDirection) + } + + enum class Resolved : SwipeSource.Resolved { + Left, + Right, + Top, + Bottom, + } } val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 936f4ba0efef..a49f1af97183 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -37,7 +37,7 @@ internal class Scene( val key: SceneKey, layoutImpl: SceneTransitionLayoutImpl, content: @Composable SceneScope.() -> Unit, - actions: Map<UserAction, UserActionResult>, + actions: Map<UserAction.Resolved, UserActionResult>, zIndex: Float, ) { internal val scope = SceneScopeImpl(layoutImpl, this) @@ -54,8 +54,8 @@ internal class Scene( } private fun checkValid( - userActions: Map<UserAction, UserActionResult> - ): Map<UserAction, UserActionResult> { + userActions: Map<UserAction.Resolved, UserActionResult> + ): Map<UserAction.Resolved, UserActionResult> { userActions.forEach { (action, result) -> if (key == result.toScene) { error( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 45758c53d69a..0c467b181cd8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -28,10 +28,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import com.android.compose.animation.scene.UserAction.Resolved /** * [SceneTransitionLayout] is a container that automatically animates its content whenever its state @@ -344,34 +347,71 @@ interface ElementBoxScope { @Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope /** An action performed by the user. */ -sealed interface UserAction { +sealed class UserAction { infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> { return this to UserActionResult(toScene = scene) } + + /** Resolve this into a [Resolved] user action given [layoutDirection]. */ + internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved + + /** A resolved [UserAction] that does not depend on the layout direction. */ + internal sealed class Resolved } /** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */ -data object Back : UserAction +data object Back : UserAction() { + override fun resolve(layoutDirection: LayoutDirection): Resolved = Resolved + + internal object Resolved : UserAction.Resolved() +} /** The user swiped on the container. */ data class Swipe( val direction: SwipeDirection, val pointerCount: Int = 1, val fromSource: SwipeSource? = null, -) : UserAction { +) : UserAction() { companion object { val Left = Swipe(SwipeDirection.Left) val Up = Swipe(SwipeDirection.Up) val Right = Swipe(SwipeDirection.Right) val Down = Swipe(SwipeDirection.Down) + val Start = Swipe(SwipeDirection.Start) + val End = Swipe(SwipeDirection.End) + } + + override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved { + return Resolved( + direction = direction.resolve(layoutDirection), + pointerCount = pointerCount, + fromSource = fromSource?.resolve(layoutDirection), + ) } + + /** A resolved [Swipe] that does not depend on the layout direction. */ + internal data class Resolved( + val direction: SwipeDirection.Resolved, + val pointerCount: Int, + val fromSource: SwipeSource.Resolved?, + ) : UserAction.Resolved() } -enum class SwipeDirection(val orientation: Orientation) { - Up(Orientation.Vertical), - Down(Orientation.Vertical), - Left(Orientation.Horizontal), - Right(Orientation.Horizontal), +enum class SwipeDirection(internal val resolve: (LayoutDirection) -> Resolved) { + Up(resolve = { Resolved.Up }), + Down(resolve = { Resolved.Down }), + Left(resolve = { Resolved.Left }), + Right(resolve = { Resolved.Right }), + Start(resolve = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }), + End(resolve = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left }); + + /** A resolved [SwipeDirection] that does not depend on the layout direction. */ + internal enum class Resolved(val orientation: Orientation) { + Up(Orientation.Vertical), + Down(Orientation.Vertical), + Left(Orientation.Horizontal), + Right(Orientation.Horizontal), + } } /** @@ -386,6 +426,16 @@ interface SwipeSource { override fun equals(other: Any?): Boolean override fun hashCode(): Int + + /** Resolve this into a [Resolved] swipe source given [layoutDirection]. */ + fun resolve(layoutDirection: LayoutDirection): Resolved + + /** A resolved [SwipeSource] that does not depend on the layout direction. */ + interface Resolved { + override fun equals(other: Any?): Boolean + + override fun hashCode(): Int + } } interface SwipeSourceDetector { @@ -460,11 +510,13 @@ internal fun SceneTransitionLayoutForTesting( scenes: SceneTransitionLayoutScope.() -> Unit, ) { val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current val coroutineScope = rememberCoroutineScope() val layoutImpl = remember { SceneTransitionLayoutImpl( state = state as BaseSceneTransitionLayoutState, density = density, + layoutDirection = layoutDirection, swipeSourceDetector = swipeSourceDetector, transitionInterceptionThreshold = transitionInterceptionThreshold, builder = scenes, @@ -475,7 +527,7 @@ internal fun SceneTransitionLayoutForTesting( // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a // SnapshotStateMap anymore. - layoutImpl.updateScenes(scenes) + layoutImpl.updateScenes(scenes, layoutDirection) SideEffect { if (state != layoutImpl.state) { @@ -486,6 +538,7 @@ internal fun SceneTransitionLayoutForTesting( } layoutImpl.density = density + layoutImpl.layoutDirection = layoutDirection layoutImpl.swipeSourceDetector = swipeSourceDetector layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 609541925bc5..3e48c429ba7d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachReversed import com.android.compose.ui.util.lerp @@ -45,6 +46,7 @@ internal typealias MovableElementContent = @Composable (@Composable () -> Unit) internal class SceneTransitionLayoutImpl( internal val state: BaseSceneTransitionLayoutState, internal var density: Density, + internal var layoutDirection: LayoutDirection, internal var swipeSourceDetector: SwipeSourceDetector, internal var transitionInterceptionThreshold: Float, builder: SceneTransitionLayoutScope.() -> Unit, @@ -114,7 +116,7 @@ internal class SceneTransitionLayoutImpl( private set init { - updateScenes(builder) + updateScenes(builder, layoutDirection) // DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the // current scene (required for SwipeTransition). @@ -147,7 +149,10 @@ internal class SceneTransitionLayoutImpl( return scenes[key] ?: error("Scene $key is not configured") } - internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) { + internal fun updateScenes( + builder: SceneTransitionLayoutScope.() -> Unit, + layoutDirection: LayoutDirection, + ) { // Keep a reference of the current scenes. After processing [builder], the scenes that were // not configured will be removed. val scenesToRemove = scenes.keys.toMutableSet() @@ -163,11 +168,13 @@ internal class SceneTransitionLayoutImpl( ) { scenesToRemove.remove(key) + val resolvedUserActions = + userActions.mapKeys { it.key.resolve(layoutDirection) } val scene = scenes[key] if (scene != null) { // Update an existing scene. scene.content = content - scene.userActions = userActions + scene.userActions = resolvedUserActions scene.zIndex = zIndex } else { // New scene. @@ -176,7 +183,7 @@ internal class SceneTransitionLayoutImpl( key, this@SceneTransitionLayoutImpl, content, - userActions, + resolvedUserActions, zIndex, ) } @@ -213,7 +220,7 @@ internal class SceneTransitionLayoutImpl( @Composable private fun BackHandler() { val targetSceneForBack = - scene(state.transitionState.currentScene).userActions[Back]?.toScene + scene(state.transitionState.currentScene).userActions[Back.Resolved]?.toScene PredictiveBackHandler(state, coroutineScope, targetSceneForBack) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index 171e2430c004..aeb62628a8f4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -98,7 +98,9 @@ private class SwipeToSceneNode( /** Whether swipe should be enabled in the given [orientation]. */ private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean { - return userActions.keys.any { it is Swipe && it.direction.orientation == orientation } + return userActions.keys.any { + it is Swipe.Resolved && it.direction.orientation == orientation + } } private fun startDragImmediately(startedPosition: Offset): Boolean { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index aa8dc38fdd8f..7daefd0d5d77 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt @@ -44,26 +44,26 @@ internal class EdgeTranslate( return value } - return when (edge) { - Edge.Top -> + return when (edge.resolve(layoutImpl.layoutDirection)) { + Edge.Resolved.Top -> if (startsOutsideLayoutBounds) { Offset(value.x, -elementSize.height.toFloat()) } else { Offset(value.x, 0f) } - Edge.Left -> + Edge.Resolved.Left -> if (startsOutsideLayoutBounds) { Offset(-elementSize.width.toFloat(), value.y) } else { Offset(0f, value.y) } - Edge.Bottom -> + Edge.Resolved.Bottom -> if (startsOutsideLayoutBounds) { Offset(value.x, sceneSize.height.toFloat()) } else { Offset(value.x, (sceneSize.height - elementSize.height).toFloat()) } - Edge.Right -> + Edge.Resolved.Right -> if (startsOutsideLayoutBounds) { Offset(sceneSize.width.toFloat(), value.y) } else { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index ff83d4b93af8..7a5a84e2c3f1 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.Velocity import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes @@ -61,8 +62,24 @@ class DraggableHandlerTest { canChangeScene = { canChangeScene(it) }, ) - val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC) - val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA) + var layoutDirection = LayoutDirection.Rtl + set(value) { + field = value + layoutImpl.updateScenes(scenesBuilder, layoutDirection) + } + + var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC) + set(value) { + field = value + layoutImpl.updateScenes(scenesBuilder, layoutDirection) + } + + var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA) + set(value) { + field = value + layoutImpl.updateScenes(scenesBuilder, layoutDirection) + } + private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = { scene( key = SceneA, @@ -94,6 +111,7 @@ class DraggableHandlerTest { SceneTransitionLayoutImpl( state = layoutState, density = Density(1f), + layoutDirection = LayoutDirection.Ltr, swipeSourceDetector = DefaultEdgeDetector, transitionInterceptionThreshold = transitionInterceptionThreshold, builder = scenesBuilder, @@ -466,10 +484,8 @@ class DraggableHandlerTest { dragController1.onDragStopped(velocity = -velocityThreshold) assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB) - mutableUserActionsA.remove(Swipe.Up) - mutableUserActionsA.remove(Swipe.Down) - mutableUserActionsB.remove(Swipe.Up) - mutableUserActionsB.remove(Swipe.Down) + mutableUserActionsA = emptyMap() + mutableUserActionsB = emptyMap() // start accelaratedScroll and scroll over to B -> null val dragController2 = onDragStartedImmediately() @@ -495,7 +511,7 @@ class DraggableHandlerTest { val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f) - mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC) + mutableUserActionsA += Swipe.Up to UserActionResult(SceneC) dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f)) // target stays B even though UserActions changed assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f) @@ -512,7 +528,7 @@ class DraggableHandlerTest { val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f) - mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC) + mutableUserActionsA += Swipe.Up to UserActionResult(SceneC) dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f)) dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f)) @@ -1149,8 +1165,7 @@ class DraggableHandlerTest { overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) } } - mutableUserActionsA.clear() - mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB) + mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB)) val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f)) @@ -1178,8 +1193,7 @@ class DraggableHandlerTest { overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) } } - mutableUserActionsA.clear() - mutableUserActionsA[Swipe.Down] = UserActionResult(SceneC) + mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC)) val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f)) @@ -1220,7 +1234,8 @@ class DraggableHandlerTest { @Test fun requireFullDistanceSwipe() = runGestureTest { - mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB, requiresFullDistanceSwipe = true) + mutableUserActionsA += + Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true) val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.9f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.9f) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 25ea2eef85e9..0766e00bfccc 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -23,11 +23,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assertPositionInRootIsEqualTo @@ -37,10 +39,12 @@ import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeWithVelocity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB +import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.subjects.assertThat import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -634,4 +638,152 @@ class SwipeToSceneTest { // Foo should be translated by (20dp, 30dp). rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp) } + + @Test + fun startEnd_ltrLayout() { + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + initialScene = SceneA, + transitions = + transitions { + from(SceneA, to = SceneB) { + // We go to B by swiping to the start (left in LTR), so we make + // scene B appear from the end (right) edge. + translate(SceneB.rootElementKey, Edge.End) + } + + from(SceneA, to = SceneC) { + // We go to C by swiping to the end (right in LTR), so we make + // scene C appear from the start (left) edge. + translate(SceneC.rootElementKey, Edge.Start) + } + }, + ) + } + + val layoutSize = 200.dp + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(state, Modifier.size(layoutSize)) { + scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) { + Box(Modifier.fillMaxSize()) + } + scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) } + scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) } + } + } + + // Swipe to the left (start). + rule.onRoot().performTouchInput { + val middle = (layoutSize / 2).toPx() + down(Offset(middle, middle)) + moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000) + } + + // Scene B should come from the right (end) edge. + var transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneB) + rule + .onNode(isElement(SceneB.rootElementKey)) + .assertPositionInRootIsEqualTo(layoutSize, 0.dp) + + // Release to go back to A. + rule.onRoot().performTouchInput { up() } + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneA) + + // Swipe to the right (end). + rule.onRoot().performTouchInput { + val middle = (layoutSize / 2).toPx() + down(Offset(middle, middle)) + moveBy(Offset(touchSlop, 0f), delayMillis = 1_000) + } + + // Scene C should come from the left (start) edge. + transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneC) + rule + .onNode(isElement(SceneC.rootElementKey)) + .assertPositionInRootIsEqualTo(-layoutSize, 0.dp) + } + + @Test + fun startEnd_rtlLayout() { + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + initialScene = SceneA, + transitions = + transitions { + from(SceneA, to = SceneB) { + // We go to B by swiping to the start (right in RTL), so we make + // scene B appear from the end (left) edge. + translate(SceneB.rootElementKey, Edge.End) + } + + from(SceneA, to = SceneC) { + // We go to C by swiping to the end (left in RTL), so we make + // scene C appear from the start (right) edge. + translate(SceneC.rootElementKey, Edge.Start) + } + }, + ) + } + + val layoutSize = 200.dp + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + SceneTransitionLayout(state, Modifier.size(layoutSize)) { + scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) { + Box(Modifier.fillMaxSize()) + } + scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) } + scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) } + } + } + } + + // Swipe to the left (end). + rule.onRoot().performTouchInput { + val middle = (layoutSize / 2).toPx() + down(Offset(middle, middle)) + moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000) + } + + // Scene C should come from the right (start) edge. + var transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneC) + rule + .onNode(isElement(SceneC.rootElementKey)) + .assertPositionInRootIsEqualTo(layoutSize, 0.dp) + + // Release to go back to A. + rule.onRoot().performTouchInput { up() } + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneA) + + // Swipe to the right (start). + rule.onRoot().performTouchInput { + val middle = (layoutSize / 2).toPx() + down(Offset(middle, middle)) + moveBy(Offset(touchSlop, 0f), delayMillis = 1_000) + } + + // Scene C should come from the left (end) edge. + transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneB) + rule + .onNode(isElement(SceneB.rootElementKey)) + .assertPositionInRootIsEqualTo(-layoutSize, 0.dp) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index da89eea35f4c..766c391b14d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -526,7 +526,7 @@ public final class KeyboardShortcuts { keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( mContext.getString(R.string.keyboard_shortcut_group_applications_assist), assistIcon, - KeyEvent.KEYCODE_UNKNOWN, + KeyEvent.KEYCODE_A, KeyEvent.META_META_ON)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index 97791acfb43a..316e1f13bc2b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -22,6 +22,7 @@ import android.content.res.Resources import android.hardware.biometrics.BiometricSourceType import android.provider.Settings import com.android.app.tracing.ListenersTracing.forEachTraced +import com.android.app.tracing.coroutines.launch import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -159,7 +160,7 @@ class KeyguardBypassController @Inject constructor( } fun listenForQsExpandedChange() = - applicationScope.launch { + applicationScope.launch("listenForQsExpandedChange") { shadeInteractorLazy.get().qsExpansion.map { it > 0f }.distinctUntilChanged() .collect { isQsExpanded -> val changed = qsExpanded != isQsExpanded diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java index 996859027e91..8ca045834981 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java @@ -220,6 +220,11 @@ final class AdditionalSubtypeMapRepository { } @AnyThread + static void onUserCreated(@UserIdInt int userId) { + sWriter.onUserCreated(userId); + } + + @AnyThread static void remove(@UserIdInt int userId, @NonNull Handler ioHandler) { sWriter.onUserRemoved(userId); ioHandler.post(() -> { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index e342c78edad0..f5faeef9258c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -176,7 +176,6 @@ import com.android.server.AccessibilityManagerInternal; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.ServiceThread; -import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.input.InputManagerInternal; @@ -1013,7 +1012,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void onUserCreated(UserInfo user, @Nullable Object token) { // Called directly from UserManagerService. Do not block the calling thread. - initializeUsersAsync(new int[user.id]); + final int userId = user.id; + AdditionalSubtypeMapRepository.onUserCreated(userId); + initializeUsersAsync(new int[userId]); } @Override @@ -1390,9 +1391,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. getPackageManagerForUser(mContext, currentUserId), newSettings.getEnabledInputMethodList()); - final var unused = SystemServerInitThreadPool.submit( - AdditionalSubtypeMapRepository::startWriterThread, - "Start AdditionalSubtypeMapRepository's writer thread"); + AdditionalSubtypeMapRepository.startWriterThread(); if (mConcurrentMultiUserModeEnabled) { for (int userId : mUserManagerInternal.getUserIds()) { diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 56e459057bfa..46585a50ea36 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -80,6 +80,7 @@ import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.annotations.KeepForWeakReference; import com.android.internal.content.PackageMonitor; import com.android.internal.content.om.OverlayConfig; import com.android.internal.util.ArrayUtils; @@ -261,6 +262,7 @@ public final class OverlayManagerService extends SystemService { private final OverlayActorEnforcer mActorEnforcer; + @KeepForWeakReference private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor(); private int mPrevStartedUserId = -1; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 876807485eca..3076b873058d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7173,7 +7173,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawn() + ", isAnimationSet=" + isAnimationSet); if (!w.isDrawn()) { - Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController + Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl + " pv=" + w.isVisibleByPolicy() + " mDrawState=" + winAnimator.drawStateToString() + " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 59b5da8eeb51..ff46b33571f3 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4709,6 +4709,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // Update stored global config and notify everyone about the change. mRootWindowContainer.onConfigurationChanged(mTempConfig); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) { + FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED, + values.orientation); + } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return changes; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 3652c4d11cb3..93711497f590 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4082,12 +4082,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final Transaction t = mWmService.mTransactionFactory.get(); forAllWindows(w -> { final WindowStateAnimator wsa = w.mWinAnimator; - if (wsa.mSurfaceController == null) { + if (wsa.mSurfaceControl == null) { return; } if (!mWmService.mSessions.contains(wsa.mSession)) { Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): " - + w + " surface=" + wsa.mSurfaceController + + w + " surface=" + wsa.mSurfaceControl + " token=" + w.mToken + " pid=" + w.mSession.mPid + " uid=" + w.mSession.mUid); @@ -4096,7 +4096,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTmpWindow = w; } else if (w.mActivityRecord != null && !w.mActivityRecord.isClientVisible()) { Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): " - + w + " surface=" + wsa.mSurfaceController + + w + " surface=" + wsa.mSurfaceControl + " token=" + w.mActivityRecord); ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE LEAK DESTROY: %s", w); wsa.destroySurface(t); diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 74dbd15d1399..b496a65ba4a6 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -623,7 +623,7 @@ final class InputMonitor { // occlusion detection depending on the type or if it's a trusted overlay. populateOverlayInputInfo(inputWindowHandle, w); setInputWindowInfoIfNeeded(mInputTransaction, - w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle); + w.mWinAnimator.mSurfaceControl, inputWindowHandle); return; } // Skip this window because it cannot possibly receive input. @@ -687,7 +687,7 @@ final class InputMonitor { if (w.mWinAnimator.hasSurface()) { populateInputWindowHandle(inputWindowHandle, w); setInputWindowInfoIfNeeded(mInputTransaction, - w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle); + w.mWinAnimator.mSurfaceControl, inputWindowHandle); } } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 99697de6e2fa..f2ccbc4e1aeb 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -667,7 +667,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation, boolean secure) { - final WindowSurfaceController surfaceController = winAnimator.mSurfaceController; + final SurfaceControl surfaceControl = winAnimator.mSurfaceControl; boolean leakedSurface = false; boolean killedApps = false; EventLogTags.writeWmNoSurfaceMemory(winAnimator.mWin.toString(), @@ -692,7 +692,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return; } final WindowStateAnimator wsa = w.mWinAnimator; - if (wsa.mSurfaceController != null) { + if (wsa.mSurfaceControl != null) { pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid); } }, false /* traverseTopToBottom */); @@ -717,7 +717,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // app to request another one. Slog.w(TAG_WM, "Looks like we have reclaimed some memory, clearing surface for retry."); - if (surfaceController != null) { + if (surfaceControl != null) { ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE RECOVER DESTROY: %s", winAnimator.mWin); SurfaceControl.Transaction t = mWmService.mTransactionFactory.get(); diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 31fda77a8a7d..db0374e52b1a 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -324,7 +324,7 @@ class ScreenRotationAnimation { if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) { return; } - t.setSkipScreenshot(w.mWinAnimator.mSurfaceController.mSurfaceControl, skipScreenshot); + t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot); }, false); if (!skipScreenshot) { // Use sync apply to apply the change immediately, so that the next diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 75e3e6547aa8..f5108f5bc93f 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -109,8 +109,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { private final String mStringName; SurfaceSession mSurfaceSession; private final ArrayList<WindowState> mAddedWindows = new ArrayList<>(); - /** Set of visible alert/app-overlay window surfaces connected to this session. */ - private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>(); + /** Set of visible alert/app-overlay windows connected to this session. */ + private final ArraySet<WindowState> mAlertWindows = new ArraySet<>(); private final DragDropController mDragDropController; final boolean mCanAddInternalSystemWindow; boolean mCanForceShowingInsets; @@ -769,9 +769,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { return !mAddedWindows.isEmpty(); } - void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController, - boolean visible, int type) { - + void onWindowSurfaceVisibilityChanged(WindowState window, boolean visible) { + final int type = window.mAttrs.type; if (!isSystemAlertWindowType(type)) { return; } @@ -782,7 +781,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final boolean noSystemOverlayPermission = !mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay; if (visible) { - changed = mAlertWindowSurfaces.add(surfaceController); + changed = mAlertWindows.add(window); if (type == TYPE_APPLICATION_OVERLAY) { MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type, false /* set false to only log for TYPE_APPLICATION_OVERLAY */); @@ -791,7 +790,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { true /* only log for non-TYPE_APPLICATION_OVERLAY */); } } else { - changed = mAlertWindowSurfaces.remove(surfaceController); + changed = mAlertWindows.remove(window); if (type == TYPE_APPLICATION_OVERLAY) { MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type, false /* set false to only log for TYPE_APPLICATION_OVERLAY */); @@ -802,7 +801,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } if (changed && noSystemOverlayPermission) { - if (mAlertWindowSurfaces.isEmpty()) { + if (mAlertWindows.isEmpty()) { cancelAlertWindowNotification(); } else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) { mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName); @@ -815,7 +814,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { if (changed && mPid != WindowManagerService.MY_PID) { // Notify activity manager that the process contains overlay/alert windows, so it can // adjust the importance score for the process. - setHasOverlayUi(!mAlertWindowSurfaces.isEmpty()); + setHasOverlayUi(!mAlertWindows.isEmpty()); } } @@ -859,7 +858,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } mSurfaceSession = null; mAddedWindows.clear(); - mAlertWindowSurfaces.clear(); + mAlertWindows.clear(); setHasOverlayUi(false); cancelAlertWindowNotification(); } @@ -880,7 +879,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size()); pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow); - pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces); + pw.print(" mAlertWindows="); pw.print(mAlertWindows); pw.print(" mClientDead="); pw.print(mClientDead); pw.print(" mSurfaceSession="); pw.println(mSurfaceSession); pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName); @@ -896,9 +895,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { /** @return {@code true} if there is an alert window surface on the given display. */ boolean hasAlertWindowSurfaces(DisplayContent displayContent) { - for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) { - final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i); - if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) { + for (int i = mAlertWindows.size() - 1; i >= 0; i--) { + final WindowState window = mAlertWindows.valueAt(i); + if (window.mDisplayContent == displayContent) { return true; } } diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 439c7bb4050b..561ff7db5b9d 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -1169,16 +1169,6 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - @Override - public boolean isActivityEmbedded(IBinder activityToken) { - synchronized (mGlobalLock) { - final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken); - return activity != null - ? activity.isEmbeddedInHostContainer() - : false; - } - } - @VisibleForTesting @NonNull IApplicationThread getAppThread(int pid, int uid) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 700c069a51c3..ebbf6e346e91 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2570,7 +2570,7 @@ public class WindowManagerService extends IWindowManager.Stub // surface, let the client use that, but don't create new surface at this // point. Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface"); - winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl); + winAnimator.getSurfaceControl(outSurfaceControl); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } else { if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win); @@ -2766,15 +2766,15 @@ public class WindowManagerService extends IWindowManager.Stub result |= RELAYOUT_RES_SURFACE_CHANGED; } - WindowSurfaceController surfaceController; + SurfaceControl surfaceControl; try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl"); - surfaceController = winAnimator.createSurfaceLocked(); + surfaceControl = winAnimator.createSurfaceLocked(); } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - if (surfaceController != null) { - surfaceController.getSurfaceControl(outSurfaceControl); + if (surfaceControl != null) { + winAnimator.getSurfaceControl(outSurfaceControl); ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl); } else { @@ -6773,11 +6773,11 @@ public class WindowManagerService extends IWindowManager.Stub if (windowState == null) { return false; } - WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController; - if (surfaceController == null) { + final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl; + if (surfaceControl == null) { return false; } - return surfaceController.clearWindowContentFrameStats(); + return surfaceControl.clearContentFrameStats(); } } @@ -6792,15 +6792,15 @@ public class WindowManagerService extends IWindowManager.Stub if (windowState == null) { return null; } - WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController; - if (surfaceController == null) { + final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl; + if (surfaceControl == null) { return null; } if (mTempWindowRenderStats == null) { mTempWindowRenderStats = new WindowContentFrameStats(); } WindowContentFrameStats stats = mTempWindowRenderStats; - if (!surfaceController.getWindowContentFrameStats(stats)) { + if (!surfaceControl.getContentFrameStats(stats)) { return null; } return stats; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 390e34cba79e..9ebb89dfe9b6 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1501,7 +1501,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (isDrawn()) { ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation not waiting for draw in %s, surfaceController %s", this, - winAnimator.mSurfaceController); + winAnimator.mSurfaceControl); setOrientationChanging(false); mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - mWmService.mDisplayFreezeTime); @@ -2425,7 +2425,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP ProtoLog.v(WM_DEBUG_FOCUS, "Remove client=%x, surfaceController=%s Callers=%s", System.identityHashCode(mClient.asBinder()), - mWinAnimator.mSurfaceController, + mWinAnimator.mSurfaceControl, Debug.getCallers(5)); final DisplayContent displayContent = getDisplayContent(); @@ -2436,10 +2436,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mOnBackInvokedCallbackInfo = null; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b " + "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b " + "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b " + "mDisplayFrozen=%b callers=%s", - this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit, + this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit, mHasSurface, mWinAnimator.getShown(), isAnimating(TRANSITION | PARENTS), mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION), @@ -2616,7 +2616,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + isVisibleRequestedOrAdding() + " isVisible: " + (isVisible() && mActivityRecord != null && mActivityRecord.isVisible())); if (!isVisibleRequestedOrAdding()) { - Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController + Slog.i(TAG_WM, " mSurfaceControl=" + mWinAnimator.mSurfaceControl + " relayoutCalled=" + mRelayoutCalled + " viewVis=" + mViewVisibility + " policyVis=" + isVisibleByPolicy() @@ -4457,7 +4457,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState c = mChildren.get(i); - if (c.mWinAnimator.mSurfaceController != null) { + if (c.mWinAnimator.mSurfaceControl != null) { c.performShowLocked(); // It hadn't been shown, which means layout not performed on it, so now we // want to make sure to do a layout. If called from within the transaction @@ -4914,7 +4914,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn() + ", animating=" + isAnimating(TRANSITION | PARENTS)); if (!isDrawn()) { - Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController + Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceControl + " pv=" + isVisibleByPolicy() + " mDrawState=" + mWinAnimator.mDrawState + " ph=" + isParentWindowHidden() @@ -5535,13 +5535,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // been defined and so we can use static layers and leave it that way. if (w.mAttrs.type == TYPE_APPLICATION_MEDIA) { if (mWinAnimator.hasSurface()) { - w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -2); + w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -2); } else { w.assignLayer(t, -2); } } else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) { if (mWinAnimator.hasSurface()) { - w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -1); + w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -1); } else { w.assignLayer(t, -1); } @@ -5717,7 +5717,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } SurfaceControl getClientViewRootSurface() { - return mWinAnimator.getSurfaceControl(); + return mWinAnimator.mSurfaceControl; } /** Drops a buffer for this window's view-root from a transaction */ @@ -6117,11 +6117,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } getPendingTransaction().setSecure(mSurfaceControl, isSecure); } else { - if (mWinAnimator.mSurfaceController == null - || mWinAnimator.mSurfaceController.mSurfaceControl == null) { + if (mWinAnimator.mSurfaceControl == null) { return; } - getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl, + getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl, isSecure); } if (mDisplayContent != null) { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 397a6357fb66..24a2a626fe50 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -16,6 +16,10 @@ package com.android.server.wm; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.SurfaceControl.METADATA_OWNER_PID; +import static android.view.SurfaceControl.METADATA_OWNER_UID; +import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -44,6 +48,7 @@ import static com.android.server.wm.WindowManagerService.logWithStack; import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE; import static com.android.server.wm.WindowStateAnimatorProto.SURFACE; import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT; +import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN; import static com.android.window.flags.Flags.secureWindowState; import static com.android.window.flags.Flags.setScPropertiesInClient; @@ -52,6 +57,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Debug; import android.os.Trace; +import android.util.EventLog; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Surface.OutOfResourcesException; @@ -95,12 +101,13 @@ class WindowStateAnimator { final Session mSession; final WindowManagerPolicy mPolicy; final Context mContext; - final boolean mIsWallpaper; private final WallpaperController mWallpaperControllerLocked; boolean mAnimationIsEntrance; - WindowSurfaceController mSurfaceController; + SurfaceControl mSurfaceControl; + private boolean mSurfaceShown; + private String mTitle; float mShownAlpha = 0; float mAlpha = 0; @@ -164,7 +171,6 @@ class WindowStateAnimator { mWin = win; mSession = win.mSession; mAttrType = win.mAttrs.type; - mIsWallpaper = win.mIsWallpaper; mWallpaperControllerLocked = win.getDisplayContent().mWallpaperController; } @@ -198,16 +204,32 @@ class WindowStateAnimator { } void hide(SurfaceControl.Transaction transaction, String reason) { - if (!mLastHidden) { - //dump(); - mLastHidden = true; + if (mLastHidden) { + return; + } + mLastHidden = true; + if (mSurfaceControl == null || !mSurfaceShown) { + return; + } + ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, mTitle); - if (mSurfaceController != null) { - mSurfaceController.hide(transaction, reason); - } + setShown(false); + transaction.hide(mSurfaceControl); + if (mWin.mIsWallpaper) { + final DisplayContent dc = mWin.getDisplayContent(); + EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE, + dc.mDisplayId, 0 /* request hidden */, + String.valueOf(dc.mWallpaperController.getWallpaperTarget())); } } + private void setShown(boolean surfaceShown) { + mSurfaceShown = surfaceShown; + mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, surfaceShown); + mWin.onSurfaceShownChanged(surfaceShown); + mSession.onWindowSurfaceVisibilityChanged(mWin, mSurfaceShown); + } + boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) { final boolean startingWindow = mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -221,7 +243,7 @@ class WindowStateAnimator { if (mDrawState == DRAW_PENDING) { ProtoLog.v(WM_DEBUG_DRAW, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin, - mSurfaceController); + mSurfaceControl); if (startingWindow) { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin); } @@ -248,7 +270,7 @@ class WindowStateAnimator { return false; } ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s", - mSurfaceController); + mSurfaceControl); mDrawState = READY_TO_SHOW; boolean result = false; final ActivityRecord activity = mWin.mActivityRecord; @@ -271,11 +293,11 @@ class WindowStateAnimator { } } - WindowSurfaceController createSurfaceLocked() { + SurfaceControl createSurfaceLocked() { final WindowState w = mWin; - if (mSurfaceController != null) { - return mSurfaceController; + if (mSurfaceControl != null) { + return mSurfaceControl; } w.setHasSurface(false); @@ -312,10 +334,22 @@ class WindowStateAnimator { final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0; final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format; - mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format, - flags, this, attrs.type); + mTitle = attrs.getTitle().toString(); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl"); + mSurfaceControl = mWin.makeSurface() + .setParent(mWin.mSurfaceControl) + .setName(mTitle) + .setFormat(format) + .setFlags(flags) + .setMetadata(METADATA_WINDOW_TYPE, attrs.type) + .setMetadata(METADATA_OWNER_UID, mSession.mUid) + .setMetadata(METADATA_OWNER_PID, mSession.mPid) + .setCallsite("WindowSurfaceController") + .setBLASTLayer().build(); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + if (!setScPropertiesInClient()) { - mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(), + setColorSpaceAgnosticLocked( (attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0); } @@ -326,7 +360,7 @@ class WindowStateAnimator { ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s", - mSurfaceController, mSession.mSurfaceSession, mSession.mPid, attrs.format, + mSurfaceControl, mSession.mSurfaceSession, mSession.mPid, attrs.format, flags, this); } catch (OutOfResourcesException e) { Slog.w(TAG, "OutOfResourcesException creating surface"); @@ -340,7 +374,7 @@ class WindowStateAnimator { } if (DEBUG) { - Slog.v(TAG, "Got surface: " + mSurfaceController + Slog.v(TAG, "Got surface: " + mSurfaceControl + ", set left=" + w.getFrame().left + " top=" + w.getFrame().top); } @@ -353,15 +387,19 @@ class WindowStateAnimator { mLastHidden = true; if (DEBUG) Slog.v(TAG, "Created surface " + this); - return mSurfaceController; + return mSurfaceControl; } boolean hasSurface() { - return mSurfaceController != null && mSurfaceController.hasSurface(); + return mSurfaceControl != null; + } + + void getSurfaceControl(SurfaceControl outSurfaceControl) { + outSurfaceControl.copyFrom(mSurfaceControl, "WindowStateAnimator.getSurfaceControl"); } void destroySurfaceLocked(SurfaceControl.Transaction t) { - if (mSurfaceController == null) { + if (mSurfaceControl == null) { return; } @@ -370,7 +408,7 @@ class WindowStateAnimator { try { if (DEBUG_VISIBILITY) { logWithStack(TAG, "Window " + this + " destroying surface " - + mSurfaceController + ", session " + mSession); + + mSurfaceControl + ", session " + mSession); } ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s", mWin, new RuntimeException().fillInStackTrace()); @@ -384,23 +422,13 @@ class WindowStateAnimator { } } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying Window " + this - + " surface " + mSurfaceController + " session " + mSession + ": " + + " surface " + mSurfaceControl + " session " + mSession + ": " + e.toString()); } - - // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it - // needs to be cleared to match the WindowState.mHasSurface state. It is also necessary - // so it can be recreated successfully in mPendingDestroySurface case. - mWin.setHasSurface(false); - if (mSurfaceController != null) { - mSurfaceController.setShown(false); - } - mSurfaceController = null; - mDrawState = NO_SURFACE; } void computeShownFrameLocked() { - if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) { + if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) { return; } else if (mWin.isDragResizeChanged()) { // This window is awaiting a relayout because user just started (or ended) @@ -454,14 +482,13 @@ class WindowStateAnimator { mLastAlpha = mShownAlpha; ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s", - mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w); + mSurfaceControl, mShownAlpha, w.mHScale, w.mVScale, w); - boolean prepared = - mSurfaceController.prepareToShowInTransaction(t, mShownAlpha); + t.setAlpha(mSurfaceControl, mShownAlpha); - if (prepared && mDrawState == HAS_DRAWN) { + if (mDrawState == HAS_DRAWN) { if (mLastHidden) { - mSurfaceController.showRobustly(t); + showRobustly(t); mLastHidden = false; final DisplayContent displayContent = w.getDisplayContent(); if (!displayContent.getLastHasContent()) { @@ -494,18 +521,38 @@ class WindowStateAnimator { } } + private void showRobustly(SurfaceControl.Transaction t) { + if (mSurfaceShown) { + return; + } + + ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", mTitle); + if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout"); + setShown(true); + t.show(mSurfaceControl); + if (mWin.mIsWallpaper) { + final DisplayContent dc = mWin.mDisplayContent; + EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE, + dc.mDisplayId, 1 /* request shown */, + String.valueOf(dc.mWallpaperController.getWallpaperTarget())); + } + } + void setOpaqueLocked(boolean isOpaque) { - if (mSurfaceController == null) { + if (mSurfaceControl == null) { return; } - mSurfaceController.setOpaque(isOpaque); + ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, mTitle); + mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque); + mService.scheduleAnimationLocked(); } void setColorSpaceAgnosticLocked(boolean agnostic) { - if (mSurfaceController == null) { + if (mSurfaceControl == null) { return; } - mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic); + ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, mTitle); + mWin.getPendingTransaction().setColorSpaceAgnostic(mSurfaceControl, agnostic); } void applyEnterAnimationLocked() { @@ -521,7 +568,7 @@ class WindowStateAnimator { // should be controlled by ActivityRecord in general. Wallpaper is also excluded because // WallpaperController should handle it. Also skip play enter animation for the window // below starting window. - if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper + if (mAttrType != TYPE_BASE_APPLICATION && !mWin.mIsWallpaper && !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) { applyAnimationLocked(transit, true); } @@ -614,8 +661,10 @@ class WindowStateAnimator { void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - if (mSurfaceController != null) { - mSurfaceController.dumpDebug(proto, SURFACE); + if (mSurfaceControl != null) { + final long dumpToken = proto.start(SURFACE); + proto.write(SHOWN, mSurfaceShown); + proto.end(dumpToken); } proto.write(DRAW_STATE, mDrawState); mSystemDecorRect.dumpDebug(proto, SYSTEM_DECOR_RECT); @@ -626,8 +675,11 @@ class WindowStateAnimator { if (mAnimationIsEntrance) { pw.print(prefix); pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance); } - if (mSurfaceController != null) { - mSurfaceController.dump(pw, prefix, dumpAll); + if (mSurfaceControl != null) { + if (dumpAll) { + pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl); + } + pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown); } if (dumpAll) { pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString()); @@ -659,31 +711,24 @@ class WindowStateAnimator { } boolean getShown() { - if (mSurfaceController != null) { - return mSurfaceController.getShown(); - } - return false; + return mSurfaceControl != null && mSurfaceShown; } void destroySurface(SurfaceControl.Transaction t) { - try { - if (mSurfaceController != null) { - mSurfaceController.destroy(t); - } - } catch (RuntimeException e) { - Slog.w(TAG, "Exception thrown when destroying surface " + this - + " surface " + mSurfaceController + " session " + mSession + ": " + e); - } finally { - mWin.setHasSurface(false); - mSurfaceController = null; - mDrawState = NO_SURFACE; + if (mSurfaceControl == null) { + return; } - } - - SurfaceControl getSurfaceControl() { - if (!hasSurface()) { - return null; + ProtoLog.i(WM_SHOW_SURFACE_ALLOC, + "Destroying surface %s called by %s", this, Debug.getCallers(8)); + if (mWin.mIsWallpaper && !mWin.mWindowRemovalAllowed && !mWin.mRemoveOnExit) { + // The wallpaper surface should have the same lifetime as its window. + Slog.e(TAG, "Unexpected removing wallpaper surface of " + mWin + + " by " + Debug.getCallers(8)); } - return mSurfaceController.mSurfaceControl; + t.remove(mSurfaceControl); + setShown(false); + mSurfaceControl = null; + mWin.setHasSurface(false); + mDrawState = NO_SURFACE; } } diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java deleted file mode 100644 index d9766e0dfa61..000000000000 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2015 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.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.SurfaceControl.METADATA_OWNER_PID; -import static android.view.SurfaceControl.METADATA_OWNER_UID; -import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; - -import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; -import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN; - -import android.os.Debug; -import android.os.Trace; -import android.util.EventLog; -import android.util.Slog; -import android.util.proto.ProtoOutputStream; -import android.view.SurfaceControl; -import android.view.WindowContentFrameStats; - -import com.android.internal.protolog.ProtoLog; - -import java.io.PrintWriter; - -class WindowSurfaceController { - static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM; - - final WindowStateAnimator mAnimator; - - SurfaceControl mSurfaceControl; - - // Should only be set from within setShown(). - private boolean mSurfaceShown = false; - - private final String title; - - private final WindowManagerService mService; - - private final int mWindowType; - private final Session mWindowSession; - - - WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator, - int windowType) { - mAnimator = animator; - - title = name; - - mService = animator.mService; - final WindowState win = animator.mWin; - mWindowType = windowType; - mWindowSession = win.mSession; - - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl"); - mSurfaceControl = win.makeSurface() - .setParent(win.getSurfaceControl()) - .setName(name) - .setFormat(format) - .setFlags(flags) - .setMetadata(METADATA_WINDOW_TYPE, windowType) - .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid) - .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid) - .setCallsite("WindowSurfaceController") - .setBLASTLayer().build(); - - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - - void hide(SurfaceControl.Transaction transaction, String reason) { - ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title); - - if (mSurfaceShown) { - hideSurface(transaction); - } - } - - private void hideSurface(SurfaceControl.Transaction transaction) { - if (mSurfaceControl == null) { - return; - } - setShown(false); - try { - transaction.hide(mSurfaceControl); - if (mAnimator.mIsWallpaper) { - final DisplayContent dc = mAnimator.mWin.getDisplayContent(); - EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE, - dc.mDisplayId, 0 /* request hidden */, - String.valueOf(dc.mWallpaperController.getWallpaperTarget())); - } - } catch (RuntimeException e) { - Slog.w(TAG, "Exception hiding surface in " + this); - } - } - - void destroy(SurfaceControl.Transaction t) { - ProtoLog.i(WM_SHOW_SURFACE_ALLOC, - "Destroying surface %s called by %s", this, Debug.getCallers(8)); - try { - if (mSurfaceControl != null) { - if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed - && !mAnimator.mWin.mRemoveOnExit) { - // The wallpaper surface should have the same lifetime as its window. - Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin - + " by " + Debug.getCallers(8)); - } - t.remove(mSurfaceControl); - } - } catch (RuntimeException e) { - Slog.w(TAG, "Error destroying surface in: " + this, e); - } finally { - setShown(false); - mSurfaceControl = null; - } - } - - boolean prepareToShowInTransaction(SurfaceControl.Transaction t, float alpha) { - if (mSurfaceControl == null) { - return false; - } - - t.setAlpha(mSurfaceControl, alpha); - return true; - } - - void setOpaque(boolean isOpaque) { - ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title); - - if (mSurfaceControl == null) { - return; - } - - mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque); - mService.scheduleAnimationLocked(); - } - - void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) { - ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title); - - if (mSurfaceControl == null) { - return; - } - t.setColorSpaceAgnostic(mSurfaceControl, agnostic); - } - - void showRobustly(SurfaceControl.Transaction t) { - ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title); - if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this - + " during relayout"); - - if (mSurfaceShown) { - return; - } - - setShown(true); - t.show(mSurfaceControl); - if (mAnimator.mIsWallpaper) { - final DisplayContent dc = mAnimator.mWin.getDisplayContent(); - EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE, - dc.mDisplayId, 1 /* request shown */, - String.valueOf(dc.mWallpaperController.getWallpaperTarget())); - } - } - - boolean clearWindowContentFrameStats() { - if (mSurfaceControl == null) { - return false; - } - return mSurfaceControl.clearContentFrameStats(); - } - - boolean getWindowContentFrameStats(WindowContentFrameStats outStats) { - if (mSurfaceControl == null) { - return false; - } - return mSurfaceControl.getContentFrameStats(outStats); - } - - boolean hasSurface() { - return mSurfaceControl != null; - } - - void getSurfaceControl(SurfaceControl outSurfaceControl) { - outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl"); - } - - boolean getShown() { - return mSurfaceShown; - } - - void setShown(boolean surfaceShown) { - mSurfaceShown = surfaceShown; - - mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown); - - mAnimator.mWin.onSurfaceShownChanged(surfaceShown); - - if (mWindowSession != null) { - mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType); - } - } - - void dumpDebug(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - proto.write(SHOWN, mSurfaceShown); - proto.end(token); - } - - public void dump(PrintWriter pw, String prefix, boolean dumpAll) { - if (dumpAll) { - pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl); - } - pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown); - } - - @Override - public String toString() { - return mSurfaceControl.toString(); - } -} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index bacde101bf36..c2a069d17446 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -68,7 +68,6 @@ import com.android.internal.inputmethod.InputBindResult; import com.android.internal.view.IInputMethodManager; import com.android.server.LocalServices; import com.android.server.ServiceThread; -import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.input.InputManagerInternal; import com.android.server.pm.UserManagerInternal; @@ -161,7 +160,7 @@ public class InputMethodManagerServiceTestBase { .strictness(Strictness.LENIENT) .spyStatic(InputMethodUtils.class) .mockStatic(ServiceManager.class) - .mockStatic(SystemServerInitThreadPool.class) + .spyStatic(AdditionalSubtypeMapRepository.class) .startMocking(); mContext = spy(InstrumentationRegistry.getInstrumentation().getContext()); @@ -234,9 +233,8 @@ public class InputMethodManagerServiceTestBase { doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( any(PackageManager.class), anyList())); - // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(), - // which is ok to be mocked out for now. - doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString())); + // The background writer thread in AdditionalSubtypeMapRepository should be stubbed out. + doNothing().when(AdditionalSubtypeMapRepository::startWriterThread); mServiceThread = new ServiceThread( diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index c1be5ca562b1..63e3e5cf865a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -714,7 +714,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Simulate when the window is exiting and cleanupAnimation invoked // (e.g. screen off during RecentsAnimation animating), will expect the window receives // onExitAnimationDone to destroy the surface when the removal is allowed. - win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class); + win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class); win1.mHasSurface = true; win1.mAnimatingExit = true; win1.mRemoveOnExit = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 11d9629cf25e..0bf27d11493b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -43,8 +43,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -758,12 +756,9 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { // Simulating win1 has shown IME and being IME layering/input target mDisplayContent.setImeLayeringTarget(win1); mDisplayContent.setImeInputTarget(win1); - mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class); mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test"); spyOn(mDisplayContent); - doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface(); - doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController) - .prepareToShowInTransaction(any(), anyFloat()); + mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class); makeWindowVisibleAndDrawn(mImeWindow); assertTrue(mImeWindow.isOnScreen()); assertFalse(mImeWindow.isParentWindowHidden()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 9b48cb9d328c..c65b76efd614 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -31,7 +31,6 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.window.flags.Flags.multiCrop; import static com.google.common.truth.Truth.assertThat; @@ -551,10 +550,9 @@ public class WallpaperControllerTests extends WindowTestsBase { } private static void makeWallpaperWindowShown(WindowState w) { - final WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class); - w.mWinAnimator.mSurfaceController = windowSurfaceController; w.mWinAnimator.mLastAlpha = 1; - when(windowSurfaceController.getShown()).thenReturn(true); + spyOn(w.mWinAnimator); + doReturn(true).when(w.mWinAnimator).getShown(); } private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 4958b904f326..89abe2ff0866 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -253,22 +253,18 @@ public class WindowManagerServiceTests extends WindowTestsBase { final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid); spyOn(session); assertTrue(session.mCanAddInternalSystemWindow); - final WindowSurfaceController winSurface = mock(WindowSurfaceController.class); - session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */, - LayoutParams.TYPE_PHONE); + final WindowState window = createWindow(null, LayoutParams.TYPE_PHONE, "win"); + session.onWindowSurfaceVisibilityChanged(window, true /* visible */); verify(session).setHasOverlayUi(true); - session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */, - LayoutParams.TYPE_PHONE); + session.onWindowSurfaceVisibilityChanged(window, false /* visible */); verify(session).setHasOverlayUi(false); } @Test public void testRelayoutExitingWindow() { final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin"); - final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class); - win.mWinAnimator.mSurfaceController = surfaceController; win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; - doReturn(true).when(surfaceController).hasSurface(); + win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class); spyOn(win.mTransitionController); doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled(); doReturn(true).when(win.mTransitionController).inTransition( @@ -306,7 +302,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { // and WMS#tryStartExitingAnimation() will destroy the surface directly. assertFalse(win.mAnimatingExit); assertFalse(win.mHasSurface); - assertNull(win.mWinAnimator.mSurfaceController); + assertNull(win.mWinAnimator.mSurfaceControl); // Invisible requested activity should not get the last config even if its view is visible. mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0, |