diff options
Diffstat (limited to 'libs')
144 files changed, 4737 insertions, 2289 deletions
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index d8f00bbc1ad3..dc4b5636a246 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -30,13 +30,20 @@ android_library_import { java_library { name: "androidx.window.sidecar", - srcs: ["src/androidx/window/sidecar/**/*.java", "src/androidx/window/util/**/*.java"], + srcs: [ + "src/androidx/window/sidecar/**/*.java", + "src/androidx/window/util/**/*.java", + "src/androidx/window/common/**/*.java", + ], static_libs: ["window-sidecar"], installable: true, sdk_version: "core_platform", system_ext_specific: true, - libs: ["framework", "androidx.annotation_annotation",], - required: ["androidx.window.sidecar.xml",], + libs: [ + "framework", + "androidx.annotation_annotation", + ], + required: ["androidx.window.sidecar.xml"], } prebuilt_etc { @@ -58,13 +65,20 @@ android_library_import { java_library { name: "androidx.window.extensions", - srcs: ["src/androidx/window/extensions/**/*.java", "src/androidx/window/util/**/*.java"], + srcs: [ + "src/androidx/window/extensions/**/*.java", + "src/androidx/window/util/**/*.java", + "src/androidx/window/common/**/*.java", + ], static_libs: ["window-extensions"], installable: true, sdk_version: "core_platform", system_ext_specific: true, - libs: ["framework", "androidx.annotation_annotation",], - required: ["androidx.window.extensions.xml",], + libs: [ + "framework", + "androidx.annotation_annotation", + ], + required: ["androidx.window.extensions.xml"], } prebuilt_etc { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java new file mode 100644 index 000000000000..e6ad011e617e --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.common; + +import static androidx.window.util.ExtensionHelper.isZero; + +import android.annotation.Nullable; +import android.graphics.Rect; + +import androidx.annotation.NonNull; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ +final class CommonDisplayFeature implements DisplayFeature { + private static final Pattern FEATURE_PATTERN = + Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); + + private static final String FEATURE_TYPE_FOLD = "fold"; + private static final String FEATURE_TYPE_HINGE = "hinge"; + + // TODO(b/183049815): Support feature strings that include the state of the feature. + /** + * Parses a display feature from a string. + * + * @throws IllegalArgumentException if the provided string is improperly formatted or could not + * otherwise be parsed. + * + * @see #FEATURE_PATTERN + */ + @NonNull + static CommonDisplayFeature parseFromString(@NonNull String string) { + Matcher featureMatcher = FEATURE_PATTERN.matcher(string); + if (!featureMatcher.matches()) { + throw new IllegalArgumentException("Malformed feature description format: " + string); + } + try { + String featureType = featureMatcher.group(1); + int type; + switch (featureType) { + case FEATURE_TYPE_FOLD: + type = 1 /* TYPE_FOLD */; + break; + case FEATURE_TYPE_HINGE: + type = 2 /* TYPE_HINGE */; + break; + default: { + throw new IllegalArgumentException("Malformed feature type: " + featureType); + } + } + + int left = Integer.parseInt(featureMatcher.group(2)); + int top = Integer.parseInt(featureMatcher.group(3)); + int right = Integer.parseInt(featureMatcher.group(4)); + int bottom = Integer.parseInt(featureMatcher.group(5)); + Rect featureRect = new Rect(left, top, right, bottom); + if (isZero(featureRect)) { + throw new IllegalArgumentException("Feature has empty bounds: " + string); + } + + return new CommonDisplayFeature(type, null, featureRect); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Malformed feature description: " + string, e); + } + } + + private final int mType; + @Nullable + private final Integer mState; + @NonNull + private final Rect mRect; + + CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) { + this.mType = type; + this.mState = state; + if (rect.width() == 0 && rect.height() == 0) { + throw new IllegalArgumentException( + "Display feature rectangle cannot have zero width and height simultaneously."); + } + this.mRect = rect; + } + + public int getType() { + return mType; + } + + /** Returns the state of the feature, or {@code null} if the feature has no state. */ + @Nullable + public Integer getState() { + return mState; + } + + @NonNull + public Rect getRect() { + return mRect; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CommonDisplayFeature that = (CommonDisplayFeature) o; + return mType == that.mType + && Objects.equals(mState, that.mState) + && mRect.equals(that.mRect); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mState, mRect); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java new file mode 100644 index 000000000000..fa9a5a8b7a1b --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.common; + +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; +import android.util.Log; +import android.util.SparseIntArray; + +import androidx.window.util.BaseDataProducer; + +import com.android.internal.R; + +import java.util.Optional; + +/** + * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture + * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources + * config at {@link R.array#config_device_state_postures}. + */ +public final class DeviceStateManagerPostureProducer extends BaseDataProducer<Integer> { + private static final String TAG = "ConfigDevicePostureProducer"; + private static final boolean DEBUG = false; + + private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); + + private int mCurrentDeviceState = INVALID_DEVICE_STATE; + + private final DeviceStateCallback mDeviceStateCallback = (state) -> { + mCurrentDeviceState = state; + notifyDataChanged(); + }; + + public DeviceStateManagerPostureProducer(@NonNull Context context) { + String[] deviceStatePosturePairs = context.getResources() + .getStringArray(R.array.config_device_state_postures); + for (String deviceStatePosturePair : deviceStatePosturePairs) { + String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); + if (deviceStatePostureMapping.length != 2) { + if (DEBUG) { + Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair); + } + continue; + } + + int deviceState; + int posture; + try { + deviceState = Integer.parseInt(deviceStatePostureMapping[0]); + posture = Integer.parseInt(deviceStatePostureMapping[1]); + } catch (NumberFormatException e) { + if (DEBUG) { + Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair, + e); + } + continue; + } + + mDeviceStateToPostureMap.put(deviceState, posture); + } + + if (mDeviceStateToPostureMap.size() > 0) { + context.getSystemService(DeviceStateManager.class) + .registerCallback(context.getMainExecutor(), mDeviceStateCallback); + } + } + + @Override + @Nullable + public Optional<Integer> getData() { + final int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, -1); + return posture != -1 ? Optional.of(posture) : Optional.empty(); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java index b74a2a4bd47d..b6c4c436d0b1 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java @@ -14,39 +14,23 @@ * limitations under the License. */ -package androidx.window.util; +package androidx.window.common; +import android.annotation.Nullable; import android.graphics.Rect; import androidx.annotation.NonNull; /** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ -public class BaseDisplayFeature { - private final int mType; - private final int mState; - @NonNull - public final Rect mRect; - - public BaseDisplayFeature(int type, int state, @NonNull Rect rect) { - this.mType = type; - this.mState = state; - if (rect.width() == 0 && rect.height() == 0) { - throw new IllegalArgumentException( - "Display feature rectangle cannot have zero width and height simultaneously."); - } - this.mRect = rect; - } - - public int getType() { - return mType; - } +public interface DisplayFeature { + /** Returns the type of the feature. */ + int getType(); - public int getState() { - return mState; - } + /** Returns the state of the feature, or {@code null} if the feature has no state. */ + @Nullable + Integer getState(); + /** Returns the bounds of the feature. */ @NonNull - public Rect getRect() { - return mRect; - } + Rect getRect(); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java new file mode 100644 index 000000000000..cd2cadc082e1 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.common; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import androidx.window.util.BaseDataProducer; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Implementation of {@link androidx.window.util.DataProducer} that produces + * {@link CommonDisplayFeature} parsed from a string stored in the resources config at + * {@link R.string#config_display_features}. + */ +public final class ResourceConfigDisplayFeatureProducer extends + BaseDataProducer<List<DisplayFeature>> { + private static final boolean DEBUG = false; + private static final String TAG = "ResourceConfigDisplayFeatureProducer"; + + private final Context mContext; + + public ResourceConfigDisplayFeatureProducer(@NonNull Context context) { + mContext = context; + } + + @Override + @Nullable + public Optional<List<DisplayFeature>> getData() { + String displayFeaturesString = mContext.getResources().getString( + R.string.config_display_features); + if (TextUtils.isEmpty(displayFeaturesString)) { + return Optional.empty(); + } + + List<DisplayFeature> features = new ArrayList<>(); + String[] featureStrings = displayFeaturesString.split(";"); + for (String featureString : featureStrings) { + CommonDisplayFeature feature; + try { + feature = CommonDisplayFeature.parseFromString(featureString); + } catch (IllegalArgumentException e) { + if (DEBUG) { + Log.w(TAG, "Failed to parse display feature: " + featureString, e); + } + continue; + } + features.add(feature); + } + return Optional.of(features); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java new file mode 100644 index 000000000000..2026df3fa979 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.common; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; + +import androidx.window.util.BaseDataProducer; + +import java.util.Optional; + +/** + * Implementation of {@link androidx.window.util.DataProducer} that provides the device posture + * as an {@link Integer} from a value stored in {@link Settings}. + */ +public final class SettingsDevicePostureProducer extends BaseDataProducer<Integer> { + private static final String DEVICE_POSTURE = "device_posture"; + + private final Uri mDevicePostureUri = + Settings.Global.getUriFor(DEVICE_POSTURE); + + private final ContentResolver mResolver; + private final ContentObserver mObserver; + private boolean mRegisteredObservers; + + public SettingsDevicePostureProducer(@NonNull Context context) { + mResolver = context.getContentResolver(); + mObserver = new SettingsObserver(); + } + + @Override + @Nullable + public Optional<Integer> getData() { + int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, -1); + return posture == -1 ? Optional.empty() : Optional.of(posture); + } + + /** + * Registers settings observers, if needed. When settings observers are registered for this + * producer callbacks for changes in data will be triggered. + */ + public void registerObserversIfNeeded() { + if (mRegisteredObservers) { + return; + } + mRegisteredObservers = true; + mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */, + mObserver /* ContentObserver */); + } + + /** + * Unregisters settings observers, if needed. When settings observers are unregistered for this + * producer callbacks for changes in data will not be triggered. + */ + public void unregisterObserversIfNeeded() { + if (!mRegisteredObservers) { + return; + } + mRegisteredObservers = false; + mResolver.unregisterContentObserver(mObserver); + } + + private final class SettingsObserver extends ContentObserver { + SettingsObserver() { + super(new Handler(Looper.getMainLooper())); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (mDevicePostureUri.equals(uri)) { + notifyDataChanged(); + } + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java new file mode 100644 index 000000000000..040662657a74 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.common; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import androidx.window.util.BaseDataProducer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Implementation of {@link androidx.window.util.DataProducer} that produces + * {@link CommonDisplayFeature} parsed from a string stored in {@link Settings}. + */ +public final class SettingsDisplayFeatureProducer + extends BaseDataProducer<List<DisplayFeature>> { + private static final boolean DEBUG = false; + private static final String TAG = "SettingsDisplayFeatureProducer"; + private static final String DISPLAY_FEATURES = "display_features"; + + private final Uri mDisplayFeaturesUri = + Settings.Global.getUriFor(DISPLAY_FEATURES); + + private final ContentResolver mResolver; + private final ContentObserver mObserver; + private boolean mRegisteredObservers; + + public SettingsDisplayFeatureProducer(@NonNull Context context) { + mResolver = context.getContentResolver(); + mObserver = new SettingsObserver(); + } + + @Override + @Nullable + public Optional<List<DisplayFeature>> getData() { + String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); + if (displayFeaturesString == null) { + return Optional.empty(); + } + + List<DisplayFeature> features = new ArrayList<>(); + if (TextUtils.isEmpty(displayFeaturesString)) { + return Optional.of(features); + } + String[] featureStrings = displayFeaturesString.split(";"); + + for (String featureString : featureStrings) { + CommonDisplayFeature feature; + try { + feature = CommonDisplayFeature.parseFromString(featureString); + } catch (IllegalArgumentException e) { + if (DEBUG) { + Log.w(TAG, "Failed to parse display feature: " + featureString, e); + } + continue; + } + features.add(feature); + } + return Optional.of(features); + } + + /** + * Registers settings observers, if needed. When settings observers are registered for this + * producer callbacks for changes in data will be triggered. + */ + public void registerObserversIfNeeded() { + if (mRegisteredObservers) { + return; + } + mRegisteredObservers = true; + mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */, + mObserver /* ContentObserver */); + } + + /** + * Unregisters settings observers, if needed. When settings observers are unregistered for this + * producer callbacks for changes in data will not be triggered. + */ + public void unregisterObserversIfNeeded() { + if (!mRegisteredObservers) { + return; + } + mRegisteredObservers = false; + mResolver.unregisterContentObserver(mObserver); + } + + private final class SettingsObserver extends ContentObserver { + SettingsObserver() { + super(new Handler(Looper.getMainLooper())); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (mDisplayFeaturesUri.equals(uri)) { + notifyDataChanged(); + } + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java index 5c91cf41bfc6..ce9be6a7bba3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java @@ -27,11 +27,17 @@ import android.graphics.Rect; import android.util.Log; import androidx.annotation.NonNull; -import androidx.window.util.BaseDisplayFeature; -import androidx.window.util.SettingsConfigProvider; +import androidx.window.common.DeviceStateManagerPostureProducer; +import androidx.window.common.DisplayFeature; +import androidx.window.common.ResourceConfigDisplayFeatureProducer; +import androidx.window.common.SettingsDevicePostureProducer; +import androidx.window.common.SettingsDisplayFeatureProducer; +import androidx.window.util.DataProducer; +import androidx.window.util.PriorityDataProducer; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Reference implementation of androidx.window.extensions OEM interface for use with @@ -41,23 +47,46 @@ import java.util.List; * production builds since the interface can still change before reaching stable version. * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead. */ -class SampleExtensionImpl extends StubExtension implements - SettingsConfigProvider.StateChangeCallback { +class SampleExtensionImpl extends StubExtension { private static final String TAG = "SampleExtension"; - private final SettingsConfigProvider mConfigProvider; + private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; + private final DataProducer<Integer> mDevicePostureProducer; + + private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; + private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; SampleExtensionImpl(Context context) { - mConfigProvider = new SettingsConfigProvider(context, this); + mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); + mDevicePostureProducer = new PriorityDataProducer<>(List.of( + mSettingsDevicePostureProducer, + new DeviceStateManagerPostureProducer(context) + )); + + mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); + mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( + mSettingsDisplayFeatureProducer, + new ResourceConfigDisplayFeatureProducer(context) + )); + + mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged); + mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } - @Override - public void onDevicePostureChanged() { - updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState())); + private void onDevicePostureChanged() { + updateDeviceState(new ExtensionDeviceState(getDevicePosture())); + + // Trigger a change in display features as the posture will be used in place of the feature + // state if the state is left unset by the producer. + onDisplayFeaturesChanged(); } - @Override - public void onDisplayFeaturesChanged() { + private int getDevicePosture() { + Optional<Integer> posture = mDevicePostureProducer.getData(); + return posture.orElse(ExtensionDeviceState.POSTURE_UNKNOWN); + } + + private void onDisplayFeaturesChanged() { for (Activity activity : getActivitiesListeningForLayoutChanges()) { ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity); updateWindowLayout(activity, newLayout); @@ -84,13 +113,20 @@ class SampleExtensionImpl extends StubExtension implements return features; } - List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures(); - for (BaseDisplayFeature baseFeature : storedFeatures) { - Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(activity, featureRect); - features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(), - baseFeature.getState())); + Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); + if (storedFeatures.isPresent()) { + int posture = getDevicePosture(); + + for (DisplayFeature baseFeature : storedFeatures.get()) { + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + + Integer featureState = baseFeature.getState(); + + features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(), + featureState == null ? posture : featureState)); + } } return features; } @@ -98,9 +134,11 @@ class SampleExtensionImpl extends StubExtension implements @Override protected void onListenersChanged() { if (hasListeners()) { - mConfigProvider.registerObserversIfNeeded(); + mSettingsDevicePostureProducer.registerObserversIfNeeded(); + mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); } else { - mConfigProvider.unregisterObserversIfNeeded(); + mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); + mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); } onDevicePostureChanged(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index d3700f88d97f..ece198cad818 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -29,33 +29,53 @@ import android.os.IBinder; import android.util.Log; import androidx.annotation.NonNull; -import androidx.window.util.BaseDisplayFeature; -import androidx.window.util.SettingsConfigProvider; +import androidx.window.common.DeviceStateManagerPostureProducer; +import androidx.window.common.DisplayFeature; +import androidx.window.common.ResourceConfigDisplayFeatureProducer; +import androidx.window.common.SettingsDevicePostureProducer; +import androidx.window.common.SettingsDisplayFeatureProducer; +import androidx.window.util.DataProducer; +import androidx.window.util.PriorityDataProducer; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Reference implementation of androidx.window.sidecar OEM interface for use with * WindowManager Jetpack. */ -class SampleSidecarImpl extends StubSidecar implements - SettingsConfigProvider.StateChangeCallback { +class SampleSidecarImpl extends StubSidecar { private static final String TAG = "SampleSidecar"; - private final SettingsConfigProvider mConfigProvider; + private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; + private final DataProducer<Integer> mDevicePostureProducer; + + private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; + private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; SampleSidecarImpl(Context context) { - mConfigProvider = new SettingsConfigProvider(context, this); + mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); + mDevicePostureProducer = new PriorityDataProducer<>(List.of( + mSettingsDevicePostureProducer, + new DeviceStateManagerPostureProducer(context) + )); + + mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); + mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( + mSettingsDisplayFeatureProducer, + new ResourceConfigDisplayFeatureProducer(context) + )); + + mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged); + mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } - @Override - public void onDevicePostureChanged() { + private void onDevicePostureChanged() { updateDeviceState(getDeviceState()); } - @Override - public void onDisplayFeaturesChanged() { + private void onDisplayFeaturesChanged() { for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); updateWindowLayout(windowToken, newLayout); @@ -65,8 +85,10 @@ class SampleSidecarImpl extends StubSidecar implements @NonNull @Override public SidecarDeviceState getDeviceState() { + Optional<Integer> posture = mDevicePostureProducer.getData(); + SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = mConfigProvider.getDeviceState(); + deviceState.posture = posture.orElse(SidecarDeviceState.POSTURE_UNKNOWN); return deviceState; } @@ -96,15 +118,17 @@ class SampleSidecarImpl extends StubSidecar implements return features; } - List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures(); - for (BaseDisplayFeature baseFeature : storedFeatures) { - SidecarDisplayFeature feature = new SidecarDisplayFeature(); - Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(activity, featureRect); - feature.setRect(featureRect); - feature.setType(baseFeature.getType()); - features.add(feature); + Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); + if (storedFeatures.isPresent()) { + for (DisplayFeature baseFeature : storedFeatures.get()) { + SidecarDisplayFeature feature = new SidecarDisplayFeature(); + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + feature.setRect(featureRect); + feature.setType(baseFeature.getType()); + features.add(feature); + } } return features; } @@ -112,9 +136,11 @@ class SampleSidecarImpl extends StubSidecar implements @Override protected void onListenersChanged() { if (hasListeners()) { - mConfigProvider.registerObserversIfNeeded(); + mSettingsDevicePostureProducer.registerObserversIfNeeded(); + mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); } else { - mConfigProvider.unregisterObserversIfNeeded(); + mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); + mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java new file mode 100644 index 000000000000..0a46703451ab --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.util; + +import androidx.annotation.NonNull; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Base class that provides the implementation for the callback mechanism of the + * {@link DataProducer} API. + * + * @param <T> The type of data this producer returns through {@link #getData()}. + */ +public abstract class BaseDataProducer<T> implements DataProducer<T> { + private final Set<Runnable> mCallbacks = new LinkedHashSet<>(); + + @Override + public final void addDataChangedCallback(@NonNull Runnable callback) { + mCallbacks.add(callback); + } + + @Override + public final void removeDataChangedCallback(@NonNull Runnable callback) { + mCallbacks.remove(callback); + } + + /** + * Called to notify all registered callbacks that the data provided by {@link #getData()} has + * changed. + */ + protected void notifyDataChanged() { + for (Runnable callback : mCallbacks) { + callback.run(); + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java new file mode 100644 index 000000000000..d4d1a23b756b --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.util; + +import android.annotation.NonNull; + +import java.util.Optional; + +/** + * Produces data through {@link #getData()} and provides a mechanism for receiving a callback when + * the data managed by the produces has changed. + * + * @param <T> The type of data this producer returns through {@link #getData()}. + */ +public interface DataProducer<T> { + /** + * Returns the data currently stored in the provider, or {@link Optional#empty()} if the + * provider has no data. + */ + Optional<T> getData(); + + /** + * Adds a callback to be notified when the data returned from {@link #getData()} has changed. + */ + void addDataChangedCallback(@NonNull Runnable callback); + + /** Removes a callback previously added with {@link #addDataChangedCallback(Runnable)}. */ + void removeDataChangedCallback(@NonNull Runnable callback); +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java new file mode 100644 index 000000000000..990ae20cc934 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.util; + +import android.annotation.Nullable; + +import java.util.List; +import java.util.Optional; + +/** + * Implementation of {@link DataProducer} that delegates calls to {@link #getData()} to the list of + * provided child producers. + * <p> + * The value returned is based on the precedence of the supplied children where the producer with + * index 0 has a higher precedence than producers that come later in the list. When a producer with + * a higher precedence has a non-empty value returned from {@link #getData()}, its value will be + * returned from an instance of this class, ignoring all other producers with lower precedence. + * + * @param <T> The type of data this producer returns through {@link #getData()}. + */ +public final class PriorityDataProducer<T> extends BaseDataProducer<T> { + private final List<DataProducer<T>> mChildProducers; + + public PriorityDataProducer(List<DataProducer<T>> childProducers) { + mChildProducers = childProducers; + for (DataProducer<T> childProducer : mChildProducers) { + childProducer.addDataChangedCallback(this::notifyDataChanged); + } + } + + @Nullable + @Override + public Optional<T> getData() { + for (DataProducer<T> childProducer : mChildProducers) { + final Optional<T> data = childProducer.getData(); + if (data.isPresent()) { + return data; + } + } + return Optional.empty(); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java deleted file mode 100644 index 6dd190ce2519..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.window.util; - -import static androidx.window.util.ExtensionHelper.isZero; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.internal.R; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Device and display feature state provider that uses Settings as the source. - */ -public final class SettingsConfigProvider extends ContentObserver { - private static final String TAG = "SettingsConfigProvider"; - private static final String DEVICE_POSTURE = "device_posture"; - private static final String DISPLAY_FEATURES = "display_features"; - - private static final Pattern FEATURE_PATTERN = - Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); - - private static final String FEATURE_TYPE_FOLD = "fold"; - private static final String FEATURE_TYPE_HINGE = "hinge"; - - private final Uri mDevicePostureUri = - Settings.Global.getUriFor(DEVICE_POSTURE); - private final Uri mDisplayFeaturesUri = - Settings.Global.getUriFor(DISPLAY_FEATURES); - private final Context mContext; - private final ContentResolver mResolver; - private final StateChangeCallback mCallback; - private boolean mRegisteredObservers; - - public SettingsConfigProvider(@NonNull Context context, @NonNull StateChangeCallback callback) { - super(new Handler(Looper.getMainLooper())); - mContext = context; - mResolver = context.getContentResolver(); - mCallback = callback; - } - - /** - * Registers the content observers for Settings keys that store device state and display feature - * configurations. - */ - public void registerObserversIfNeeded() { - if (mRegisteredObservers) { - return; - } - mRegisteredObservers = true; - mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */, - this /* ContentObserver */); - mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */, - this /* ContentObserver */); - } - - /** - * Unregisters the content observers that are tracking the state changes. - * @see #registerObserversIfNeeded() - */ - public void unregisterObserversIfNeeded() { - if (!mRegisteredObservers) { - return; - } - mRegisteredObservers = false; - mResolver.unregisterContentObserver(this); - } - - /** - * Gets the device posture int stored in Settings. - */ - public int getDeviceState() { - return Settings.Global.getInt(mResolver, DEVICE_POSTURE, - 0 /* POSTURE_UNKNOWN */); - } - - /** - * Gets the list of all display feature configs stored in Settings. Uses a custom - * {@link BaseDisplayFeature} class to report the config to be translated for actual - * containers in Sidecar or Extensions. - */ - public List<BaseDisplayFeature> getDisplayFeatures() { - List<BaseDisplayFeature> features = new ArrayList<>(); - String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); - if (TextUtils.isEmpty(displayFeaturesString)) { - displayFeaturesString = mContext.getResources().getString( - R.string.config_display_features); - } - if (TextUtils.isEmpty(displayFeaturesString)) { - return features; - } - String[] featureStrings = displayFeaturesString.split(";"); - - int deviceState = getDeviceState(); - - for (String featureString : featureStrings) { - Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString); - if (!featureMatcher.matches()) { - Log.e(TAG, "Malformed feature description format: " + featureString); - continue; - } - try { - String featureType = featureMatcher.group(1); - int type; - switch (featureType) { - case FEATURE_TYPE_FOLD: - type = 1 /* TYPE_FOLD */; - break; - case FEATURE_TYPE_HINGE: - type = 2 /* TYPE_HINGE */; - break; - default: { - Log.e(TAG, "Malformed feature type: " + featureType); - continue; - } - } - - int left = Integer.parseInt(featureMatcher.group(2)); - int top = Integer.parseInt(featureMatcher.group(3)); - int right = Integer.parseInt(featureMatcher.group(4)); - int bottom = Integer.parseInt(featureMatcher.group(5)); - Rect featureRect = new Rect(left, top, right, bottom); - if (!isZero(featureRect)) { - BaseDisplayFeature feature = new BaseDisplayFeature(type, deviceState, - featureRect); - features.add(feature); - } else { - Log.w(TAG, "Read empty feature"); - } - } catch (NumberFormatException e) { - Log.e(TAG, "Malformed feature description: " + featureString); - } - } - return features; - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (uri == null) { - return; - } - - if (mDevicePostureUri.equals(uri)) { - mCallback.onDevicePostureChanged(); - mCallback.onDisplayFeaturesChanged(); - return; - } - if (mDisplayFeaturesUri.equals(uri)) { - mCallback.onDisplayFeaturesChanged(); - } - } - - /** - * Callback that notifies about device or display feature state changes. - */ - public interface StateChangeCallback { - /** - * Notifies about the device state update. - */ - void onDevicePostureChanged(); - - /** - * Notifies about the display feature config update. - */ - void onDisplayFeaturesChanged(); - } -} diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 1b5dc8bdbcaa..18f019dc949b 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -38,6 +38,14 @@ filegroup { path: "src", } +filegroup { + name: "wm_shell-aidls", + srcs: [ + "src/**/*.aidl", + ], + path: "src", +} + // TODO(b/168581922) protologtool do not support kotlin(*.kt) filegroup { name: "wm_shell-sources-kt", @@ -98,7 +106,7 @@ android_library { ":wm_shell_protolog_src", // TODO(b/168581922) protologtool do not support kotlin(*.kt) ":wm_shell-sources-kt", - "src/**/I*.aidl", + ":wm_shell-aidls", ], resource_dirs: [ "res", @@ -110,14 +118,14 @@ android_library { "androidx.appcompat_appcompat", "androidx.arch.core_core-runtime", "androidx.dynamicanimation_dynamicanimation", + "androidx.recyclerview_recyclerview", "kotlinx-coroutines-android", "kotlinx-coroutines-core", "iconloader_base", "jsr330", "protolog-lib", - "SettingsLib", "WindowManager-Shell-proto", - "jsr330" + "jsr330", ], kotlincflags: ["-Xjvm-default=enable"], manifest: "AndroidManifest.xml", diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index d2b3cf6a4fe2..6bd0e0a4ff86 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -17,6 +17,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.wm.shell"> + <!-- System permission required by WM Shell Task Organizer. --> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> </manifest> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index b75ee4507e94..6edaf6fd6c35 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -22,9 +22,9 @@ <string name="pip_phone_settings" msgid="5468987116750491918">"הגדרות"</string> <string name="pip_menu_title" msgid="5393619322111827096">"תפריט"</string> <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> במצב תמונה בתוך תמונה"</string> - <string name="pip_notification_message" msgid="8854051911700302620">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש להקיש כדי לפתוח את ההגדרות ולכבות את התכונה."</string> + <string name="pip_notification_message" msgid="8854051911700302620">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש להקיש כדי לפתוח את ההגדרות ולהשבית את התכונה."</string> <string name="pip_play" msgid="3496151081459417097">"הפעלה"</string> - <string name="pip_pause" msgid="690688849510295232">"השהה"</string> + <string name="pip_pause" msgid="690688849510295232">"השהיה"</string> <string name="pip_skip_to_next" msgid="8403429188794867653">"אפשר לדלג אל הבא"</string> <string name="pip_skip_to_prev" msgid="7172158111196394092">"אפשר לדלג אל הקודם"</string> <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"שינוי גודל"</string> @@ -41,13 +41,13 @@ <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"מסך עליון מלא"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"עליון 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"עליון 50%"</string> - <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"עליון 30%"</string> + <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"למעלה 30%"</string> <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"מסך תחתון מלא"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"איך להשתמש במצב שימוש ביד אחת"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"הפעלה של מצב שימוש ביד אחת"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"יציאה ממצב שימוש ביד אחת"</string> - <string name="bubbles_settings_button_description" msgid="1301286017420516912">"הגדרות בשביל בועות של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="bubbles_settings_button_description" msgid="1301286017420516912">"הגדרות לבועות של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"גלישה"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"הוספה בחזרה לערימה"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> מהאפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 1527e8983079..fcd706f7e732 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -58,14 +58,14 @@ <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Naar rechtsonder verplaatsen"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"Instellingen voor <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> <string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubbel sluiten"</string> - <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels weergeven"</string> + <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels tonen"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatten met bubbels"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nieuwe gesprekken worden weergegeven als zwevende iconen of \'bubbels\'. Tik om een bubbel te openen. Sleep om de bubbel te verplaatsen."</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nieuwe gesprekken worden als zwevende iconen of bubbels getoond. Tik om een bubbel te openen. Sleep om een bubbel te verplaatsen."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Beheer bubbels wanneer je wilt"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tik op Beheren om bubbels van deze app uit te schakelen"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Geen recente bubbels"</string> - <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels worden hier weergegeven"</string> + <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels zie je hier"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubbel gesloten."</string> diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index c2f591b9d7af..0e783779866e 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -15,7 +15,13 @@ limitations under the License. --> <resources> - <!-- Animation duration for resizing of PIP when entering/exiting. --> + <!-- Animation duration for PIP when entering. --> + <integer name="config_pipEnterAnimationDuration">425</integer> + + <!-- Animation duration for PIP when exiting. --> + <integer name="config_pipExitAnimationDuration">250</integer> + + <!-- Animation duration for resizing of PIP. --> <integer name="config_pipResizeAnimationDuration">425</integer> <!-- Allow dragging the PIP to a location to close it --> @@ -43,12 +49,6 @@ when the PIP menu is shown in center. --> <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string> - <!-- one handed background panel default color RGB --> - <item name="config_one_handed_background_rgb" format="float" type="dimen">0.5</item> - - <!-- one handed background panel default alpha --> - <item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item> - <!-- maximum animation duration for the icon when entering the starting window --> <integer name="max_starting_window_intro_icon_anim_duration">1000</integer> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index cb54021d7a23..34c66a4f4b82 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -16,6 +16,8 @@ package com.android.wm.shell; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; + import android.annotation.UiContext; import android.app.ResourcesManager; import android.content.Context; @@ -34,6 +36,8 @@ import android.window.DisplayAreaOrganizer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.protolog.common.ProtoLog; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -44,14 +48,14 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { private static final String TAG = RootTaskDisplayAreaOrganizer.class.getSimpleName(); - // Display area info. mapped by displayIds. + /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */ private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>(); - // Display area leashes. mapped by displayIds. + /** Display area leashes, which is mapped by display IDs. */ private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>(); private final SparseArray<ArrayList<RootTaskDisplayAreaListener>> mListeners = new SparseArray<>(); - + /** {@link DisplayAreaContext} list, which is mapped by display IDs. */ private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>(); private final Context mContext; @@ -119,7 +123,7 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { listeners.get(i).onDisplayAreaAppeared(displayAreaInfo); } } - applyConfigChangesToContext(displayId, displayAreaInfo.configuration); + applyConfigChangesToContext(displayAreaInfo); } @Override @@ -161,24 +165,28 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { listeners.get(i).onDisplayAreaInfoChanged(displayAreaInfo); } } - applyConfigChangesToContext(displayId, displayAreaInfo.configuration); + applyConfigChangesToContext(displayAreaInfo); } /** - * Applies the {@link Configuration} to the {@link DisplayAreaContext} specified by - * {@code displayId}. - * - * @param displayId The ID of the {@link Display} which the {@link DisplayAreaContext} is - * associated with - * @param newConfig The propagated configuration + * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by + * {@link DisplayAreaInfo#displayId}. */ - private void applyConfigChangesToContext(int displayId, @NonNull Configuration newConfig) { + private void applyConfigChangesToContext(@NonNull DisplayAreaInfo displayAreaInfo) { + final int displayId = displayAreaInfo.displayId; + final Display display = mContext.getSystemService(DisplayManager.class) + .getDisplay(displayId); + if (display == null) { + ProtoLog.w(WM_SHELL_TASK_ORG, "The display#%d has been removed." + + " Skip following steps", displayId); + return; + } DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId); if (daContext == null) { - daContext = new DisplayAreaContext(mContext, displayId); + daContext = new DisplayAreaContext(mContext, display); mDisplayAreaContexts.put(displayId, daContext); } - daContext.updateConfigurationChanges(newConfig); + daContext.updateConfigurationChanges(displayAreaInfo.configuration); } /** @@ -228,10 +236,8 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { private final IBinder mToken = new Binder(); private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); - public DisplayAreaContext(@NonNull Context context, int displayId) { + public DisplayAreaContext(@NonNull Context context, @NonNull Display display) { super(null); - final Display display = context.getSystemService(DisplayManager.class) - .getDisplay(displayId); attachBaseContext(context.createTokenContext(mToken, display)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index eaed24d6195a..d451f4a0661b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -47,21 +47,7 @@ public final class ShellCommandHandlerImpl { private final ShellExecutor mMainExecutor; private final HandlerImpl mImpl = new HandlerImpl(); - public static ShellCommandHandler create( - ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreenController> legacySplitScreenOptional, - Optional<SplitScreenController> splitScreenOptional, - Optional<Pip> pipOptional, - Optional<OneHandedController> oneHandedOptional, - Optional<HideDisplayCutoutController> hideDisplayCutout, - Optional<AppPairsController> appPairsOptional, - ShellExecutor mainExecutor) { - return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional, - splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, - appPairsOptional, mainExecutor).mImpl; - } - - private ShellCommandHandlerImpl( + public ShellCommandHandlerImpl( ShellTaskOrganizer shellTaskOrganizer, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, @@ -80,6 +66,10 @@ public final class ShellCommandHandlerImpl { mMainExecutor = mainExecutor; } + public ShellCommandHandler asShellCommandHandler() { + return mImpl; + } + /** Dumps WM Shell internal state. */ private void dump(PrintWriter pw) { mShellTaskOrganizer.dump(pw, ""); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index 85bd24c1c2bf..d1fbf31e2b99 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -19,6 +19,7 @@ package com.android.wm.shell; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import com.android.wm.shell.apppairs.AppPairsController; +import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; @@ -26,7 +27,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.splitscreen.SplitScreenController; -import com.android.wm.shell.startingsurface.StartingSurface; +import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.Transitions; import java.util.Optional; @@ -40,6 +41,7 @@ public class ShellInitImpl { private final DisplayImeController mDisplayImeController; private final DragAndDropController mDragAndDropController; private final ShellTaskOrganizer mShellTaskOrganizer; + private final Optional<BubbleController> mBubblesOptional; private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<AppPairsController> mAppPairsOptional; @@ -47,48 +49,26 @@ public class ShellInitImpl { private final FullscreenTaskListener mFullscreenTaskListener; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; - private final Optional<StartingSurface> mStartingSurfaceOptional; + private final StartingWindowController mStartingWindow; private final InitImpl mImpl = new InitImpl(); - public static ShellInit create(DisplayImeController displayImeController, + public ShellInitImpl(DisplayImeController displayImeController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, + Optional<BubbleController> bubblesOptional, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, - Optional<StartingSurface> startingSurfaceOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, - FullscreenTaskListener fullscreenTaskListener, - Transitions transitions, - ShellExecutor mainExecutor) { - return new ShellInitImpl(displayImeController, - dragAndDropController, - shellTaskOrganizer, - legacySplitScreenOptional, - splitScreenOptional, - appPairsOptional, - startingSurfaceOptional, - pipTouchHandlerOptional, - fullscreenTaskListener, - transitions, - mainExecutor).mImpl; - } - - private ShellInitImpl(DisplayImeController displayImeController, - DragAndDropController dragAndDropController, - ShellTaskOrganizer shellTaskOrganizer, - Optional<LegacySplitScreenController> legacySplitScreenOptional, - Optional<SplitScreenController> splitScreenOptional, - Optional<AppPairsController> appPairsOptional, - Optional<StartingSurface> startingSurfaceOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Transitions transitions, + StartingWindowController startingWindow, ShellExecutor mainExecutor) { mDisplayImeController = displayImeController; mDragAndDropController = dragAndDropController; mShellTaskOrganizer = shellTaskOrganizer; + mBubblesOptional = bubblesOptional; mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mAppPairsOptional = appPairsOptional; @@ -96,21 +76,26 @@ public class ShellInitImpl { mPipTouchHandlerOptional = pipTouchHandlerOptional; mTransitions = transitions; mMainExecutor = mainExecutor; - mStartingSurfaceOptional = startingSurfaceOptional; + mStartingWindow = startingWindow; + } + + public ShellInit asShellInit() { + return mImpl; } private void init() { // Start listening for display changes mDisplayImeController.startMonitorDisplays(); + // Setup the shell organizer mShellTaskOrganizer.addListenerForType( mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN); - mStartingSurfaceOptional.ifPresent(mShellTaskOrganizer::initStartingSurface); - // Register the shell organizer + mShellTaskOrganizer.initStartingWindow(mStartingWindow); mShellTaskOrganizer.registerOrganizer(); mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered); mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered); + mBubblesOptional.ifPresent(BubbleController::initialize); // Bind the splitscreen impl to the drag drop controller mDragAndDropController.initialize(mSplitScreenOptional); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index fcb53cd890b7..94d13eab4299 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -48,7 +48,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sizecompatui.SizeCompatUIController; -import com.android.wm.shell.startingsurface.StartingSurface; +import com.android.wm.shell.startingsurface.StartingWindowController; import java.io.PrintWriter; import java.util.ArrayList; @@ -133,7 +133,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>(); private final Object mLock = new Object(); - private StartingSurface mStartingSurface; + private StartingWindowController mStartingWindow; /** * In charge of showing size compat UI. Can be {@code null} if device doesn't support size @@ -184,8 +184,8 @@ public class ShellTaskOrganizer extends TaskOrganizer { /** * @hide */ - public void initStartingSurface(StartingSurface startingSurface) { - mStartingSurface = startingSurface; + public void initStartingWindow(StartingWindowController startingWindow) { + mStartingWindow = startingWindow; } /** @@ -302,23 +302,23 @@ public class ShellTaskOrganizer extends TaskOrganizer { @Override public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { - if (mStartingSurface != null) { - mStartingSurface.addStartingWindow(info, appToken); + if (mStartingWindow != null) { + mStartingWindow.addStartingWindow(info, appToken); } } @Override public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, boolean playRevealAnimation) { - if (mStartingSurface != null) { - mStartingSurface.removeStartingWindow(taskId, leash, frame, playRevealAnimation); + if (mStartingWindow != null) { + mStartingWindow.removeStartingWindow(taskId, leash, frame, playRevealAnimation); } } @Override public void copySplashScreenView(int taskId) { - if (mStartingSurface != null) { - mStartingSurface.copySplashScreenView(taskId); + if (mStartingWindow != null) { + mStartingWindow.copySplashScreenView(taskId); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index 46884fefd69c..7d65a08ce798 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -83,6 +83,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private boolean mIsInitialized; private Listener mListener; private Executor mListenerExecutor; + private Rect mObscuredTouchRect; private final Rect mTmpRect = new Rect(); private final Rect mTmpRootRect = new Rect(); @@ -161,6 +162,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, } /** + * Indicates a region of the view that is not touchable. + * + * @param obscuredRect the obscured region of the view. + */ + public void setObscuredTouchRect(Rect obscuredRect) { + mObscuredTouchRect = obscuredRect; + } + + /** * Call when view position or size has changed. Do not call when animating. */ public void onLocationChanged() { @@ -384,6 +394,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, mTmpRect.set(mTmpLocation[0], mTmpLocation[1], mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight()); inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE); + + if (mObscuredTouchRect != null) { + inoutInfo.touchableRegion.union(mObscuredTouchRect); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index 562b32b41dd2..b6d408afd703 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerToken; @@ -88,7 +89,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s", task1.taskId, task2.taskId, this); - if (!task1.isResizeable || !task2.isResizeable) { + if ((!task1.isResizeable || !task2.isResizeable) + && !ActivityTaskManager.supportsNonResizableMultiWindow()) { ProtoLog.e(WM_SHELL_TASK_ORG, "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b", task1.isResizeable, task2.isResizeable); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 620c382ef183..4ff1ce97ba3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -120,6 +120,16 @@ public class BubbleController { @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; + private final WindowManagerShellWrapper mWindowManagerShellWrapper; + private final LauncherApps mLauncherApps; + private final IStatusBarService mBarService; + private final WindowManager mWindowManager; + private final ShellTaskOrganizer mTaskOrganizer; + + // Used to post to main UI thread + private final ShellExecutor mMainExecutor; + private final Handler mMainHandler; + private BubbleLogger mLogger; private BubbleData mBubbleData; private View mBubbleScrim; @@ -148,12 +158,6 @@ public class BubbleController { */ @Nullable private BubbleEntry mNotifEntryToExpandOnShadeUnlock; - private IStatusBarService mBarService; - private WindowManager mWindowManager; - - // Used to post to main UI thread - private final ShellExecutor mMainExecutor; - /** LayoutParams used to add the BubbleStackView to the window manager. */ private WindowManager.LayoutParams mWmLayoutParams; /** Whether or not the BubbleStackView has been added to the WindowManager. */ @@ -177,15 +181,6 @@ public class BubbleController { private boolean mInflateSynchronously; - private ShellTaskOrganizer mTaskOrganizer; - - /** - * Whether the IME is visible, as reported by the BubbleStackView. If it is, we'll make the - * Bubbles window NOT_FOCUSABLE so that touches on the Bubbles UI doesn't steal focus from the - * ActivityView and hide the IME. - */ - private boolean mImeVisible = false; - /** true when user is in status bar unlock shade. */ private boolean mIsStatusBarShade = true; @@ -231,13 +226,28 @@ public class BubbleController { ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; + mLauncherApps = launcherApps; + mBarService = statusBarService == null + ? IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)) + : statusBarService; + mWindowManager = windowManager; + mWindowManagerShellWrapper = windowManagerShellWrapper; mFloatingContentCoordinator = floatingContentCoordinator; mDataRepository = dataRepository; mLogger = bubbleLogger; mMainExecutor = mainExecutor; - + mMainHandler = mainHandler; + mTaskOrganizer = organizer; + mSurfaceSynchronizer = synchronizer; + mCurrentUserId = ActivityManager.getCurrentUser(); mBubblePositioner = positioner; mBubbleData = data; + mSavedBubbleKeysPerUser = new SparseSetArray<>(); + mBubbleIconFactory = new BubbleIconFactory(context); + } + + public void initialize() { mBubbleData.setListener(mBubbleDataListener); mBubbleData.setSuppressionChangedListener(bubble -> { // Make sure NoMan knows suppression state so that anyone querying it can tell. @@ -261,28 +271,18 @@ public class BubbleController { }); try { - windowManagerShellWrapper.addPinnedStackListener(new BubblesImeListener()); + mWindowManagerShellWrapper.addPinnedStackListener(new BubblesImeListener()); } catch (RemoteException e) { e.printStackTrace(); } - mSurfaceSynchronizer = synchronizer; - mWindowManager = windowManager; - mBarService = statusBarService == null - ? IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)) - : statusBarService; - mSavedBubbleKeysPerUser = new SparseSetArray<>(); - mCurrentUserId = ActivityManager.getCurrentUser(); mBubbleData.setCurrentUserId(mCurrentUserId); - mBubbleIconFactory = new BubbleIconFactory(context); - mTaskOrganizer = organizer; mTaskOrganizer.addLocusIdListener((taskId, locus, visible) -> mBubbleData.onLocusVisibilityChanged(taskId, locus, visible)); - launcherApps.registerCallback(new LauncherApps.Callback() { + mLauncherApps.registerCallback(new LauncherApps.Callback() { @Override public void onPackageAdded(String s, UserHandle userHandle) {} @@ -318,7 +318,7 @@ public class BubbleController { mBubbleData.removeBubblesWithInvalidShortcuts( packageName, validShortcuts, DISMISS_SHORTCUT_REMOVED); } - }, mainHandler); + }, mMainHandler); } @VisibleForTesting @@ -527,10 +527,6 @@ public class BubbleController { } } - void onImeVisibilityChanged(boolean imeVisible) { - mImeVisible = imeVisible; - } - /** Removes the BubbleStackView from the WindowManager if it's there. */ private void removeFromWindowManagerMaybe() { if (!mAddedToWindowManager) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java index 2d9da215efb7..fe3f9ef6aa5f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java @@ -93,7 +93,7 @@ public class BubbleIconFactory extends BaseIconFactory { final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.importance_ring_stroke_width); final int importantConversationColor = mContext.getResources().getColor( - com.android.settingslib.R.color.important_conversation, null); + R.color.important_conversation, null); Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(), userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig()); Canvas c = new Canvas(badgeAndRing); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 64a44ca9e7e9..9eec48c02306 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -41,7 +41,6 @@ import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.Region; import android.os.Bundle; import android.provider.Settings; import android.util.Log; @@ -152,6 +151,7 @@ public class BubbleStackView extends FrameLayout * starting a new animation. */ private final ShellExecutor mDelayedAnimationExecutor; + private Runnable mDelayedAnimation; /** * Interface to synchronize {@link View} state and the screen. @@ -876,8 +876,6 @@ public class BubbleStackView extends FrameLayout mTaskbarScrim.setVisibility(GONE); setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> { - mBubbleController.onImeVisibilityChanged( - insets.getInsets(WindowInsets.Type.ime()).bottom > 0); if (!mIsExpanded || mIsExpansionAnimating) { return view.onApplyWindowInsets(insets); } @@ -1046,6 +1044,7 @@ public class BubbleStackView extends FrameLayout } }; + // TODO: Create ManageMenuView and move setup / animations there private void setUpManageMenu() { if (mManageMenu != null) { removeView(mManageMenu); @@ -1865,7 +1864,7 @@ public class BubbleStackView extends FrameLayout mExpandedBubble.getExpandedView().setAlphaAnimating(true); } - mDelayedAnimationExecutor.executeDelayed(() -> { + mDelayedAnimation = () -> { mExpandedViewAlphaAnimator.start(); PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); @@ -1898,7 +1897,8 @@ public class BubbleStackView extends FrameLayout } }) .start(); - }, startDelay); + }; + mDelayedAnimationExecutor.executeDelayed(mDelayedAnimation, startDelay); } private void animateCollapse() { @@ -2097,7 +2097,7 @@ public class BubbleStackView extends FrameLayout * animating flags for those animations. */ private void cancelDelayedExpandCollapseSwitchAnimations() { - mDelayedAnimationExecutor.removeAllCallbacks(); + mDelayedAnimationExecutor.removeCallbacks(mDelayedAnimation); mIsExpansionAnimating = false; mIsBubbleSwitchAnimating = false; @@ -2146,50 +2146,6 @@ public class BubbleStackView extends FrameLayout } /** - * This method is called by {@link android.app.ActivityView} because the BubbleStackView has a - * higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV). - * ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided - * touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to - * the special nature of ActivityView, it does not respect the standard - * {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for - * this purpose. - * - * BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation - * properties for performance reasons. This means that the default implementation of this method - * subtracts the entirety of the screen from the ActivityView's touchable region, resulting in - * it not receiving any touch events. This was previously addressed by returning false in the - * stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any - * touch handlers in the stack or its child views. - * - * To support touch handlers, we're overriding this method to leave the ActivityView's touchable - * region alone. The only touchable part of the stack that can ever overlap the AV is a - * dragged-out bubble that is animating back into the row of bubbles. It's not worth continually - * updating the touchable region to allow users to grab a bubble while it completes its ~50ms - * animation back to the bubble row. - * - * NOTE: Any future additions to the stack that obscure the ActivityView region will need their - * bounds subtracted here in order to receive touch events. - */ - @Override - public void subtractObscuredTouchableRegion(Region touchableRegion, View view) { - // If the notification shade is expanded, or the manage menu is open, or we are showing - // manage bubbles user education, we shouldn't let the ActivityView steal any touch events - // from any location. - if (!mIsExpanded - || mShowingManage - || (mManageEduView != null - && mManageEduView.getVisibility() == VISIBLE)) { - touchableRegion.setEmpty(); - } - } - - /** - * If you're here because you're not receiving touch events on a view that is a descendant of - * BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the - * bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView - * consumes all touch events within its bounds, even for views like the BubbleStackView that are - * above it. It ignores typical view touch handling methods like this one and - * dispatchTouchEvent. */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { @@ -2539,6 +2495,11 @@ public class BubbleStackView extends FrameLayout } mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); + if (mExpandedBubble.getExpandedView().getTaskView() != null) { + mExpandedBubble.getExpandedView().getTaskView().setObscuredTouchRect(mShowingManage + ? new Rect(0, 0, getWidth(), getHeight()) + : null); + } final boolean isLtr = getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_LTR; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java index f118b1e0b7a3..b7235a31af03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -150,6 +150,7 @@ public class DisplayLayout { mDensityDpi = dl.mDensityDpi; mHasNavigationBar = dl.mHasNavigationBar; mHasStatusBar = dl.mHasStatusBar; + mNavBarFrameHeight = dl.mNavBarFrameHeight; mNonDecorInsets.set(dl.mNonDecorInsets); mStableInsets.set(dl.mStableInsets); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java new file mode 100644 index 000000000000..b29058b1f204 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import android.Manifest; +import android.util.Slog; + +import java.util.function.Consumer; + +/** + * Helpers for working with executors + */ +public class ExecutorUtils { + + /** + * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given + * callback. + */ + public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance, + String log, Consumer<T> callback) { + executeRemoteCallWithTaskPermission(controllerInstance, log, callback, + false /* blocking */); + } + + /** + * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given + * callback. + */ + public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance, + String log, Consumer<T> callback, boolean blocking) { + if (controllerInstance == null) return; + + final RemoteCallable<T> controller = controllerInstance; + controllerInstance.getContext().enforceCallingPermission( + Manifest.permission.MANAGE_ACTIVITY_TASKS, log); + if (blocking) { + try { + controllerInstance.getRemoteCallExecutor().executeBlocking(() -> { + callback.accept((T) controller); + }); + } catch (InterruptedException e) { + Slog.e("ExecutorUtils", "Remote call failed", e); + } + } else { + controllerInstance.getRemoteCallExecutor().execute(() -> { + callback.accept((T) controller); + }); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java index a4cd3c5a583d..bfee820870f1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java @@ -18,7 +18,6 @@ package com.android.wm.shell.common; import android.annotation.NonNull; import android.os.Handler; -import android.os.Looper; /** Executor implementation which is backed by a Handler. */ public class HandlerExecutor implements ShellExecutor { @@ -47,11 +46,6 @@ public class HandlerExecutor implements ShellExecutor { } @Override - public void removeAllCallbacks() { - mHandler.removeCallbacksAndMessages(null); - } - - @Override public void removeCallbacks(@NonNull Runnable r) { mHandler.removeCallbacks(r); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java new file mode 100644 index 000000000000..30f535ba940c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import android.content.Context; + +/** + * An interface for controllers that can receive remote calls. + */ +public interface RemoteCallable<T> { + /** + * Returns a context used for permission checking. + */ + Context getContext(); + + /** + * Returns the executor to post the handler callback to. + */ + ShellExecutor getRemoteCallExecutor(); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java index 6abc8f6dda89..f729164ed303 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java @@ -16,16 +16,10 @@ package com.android.wm.shell.common; -import android.os.Looper; -import android.os.SystemClock; -import android.os.Trace; - import java.lang.reflect.Array; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -import java.util.function.BooleanSupplier; -import java.util.function.Predicate; import java.util.function.Supplier; /** @@ -94,11 +88,6 @@ public interface ShellExecutor extends Executor { void executeDelayed(Runnable runnable, long delayMillis); /** - * Removes all pending callbacks. - */ - void removeAllCallbacks(); - - /** * See {@link android.os.Handler#removeCallbacks}. */ void removeCallbacks(Runnable runnable); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index 4bb8e9b6581f..9dabec7a13d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -32,7 +32,7 @@ import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DragEvent; -import android.view.IScrollCaptureCallbacks; +import android.view.IScrollCaptureResponseListener; import android.view.IWindow; import android.view.IWindowManager; import android.view.IWindowSession; @@ -369,9 +369,9 @@ public class SystemWindows { public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {} @Override - public void requestScrollCapture(IScrollCaptureCallbacks callbacks) { + public void requestScrollCapture(IScrollCaptureResponseListener listener) { try { - callbacks.onScrollCaptureResponse( + listener.onScrollCaptureResponse( new ScrollCaptureResponse.Builder() .setDescription("Not Implemented") .build()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index bacff78e6a67..b64c796a1a43 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -27,9 +27,10 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Rect; import android.view.SurfaceControl; +import android.view.WindowInsets; +import android.view.WindowManager; import androidx.annotation.Nullable; @@ -75,7 +76,7 @@ public class SplitLayout { mDividerSize = mDividerWindowWidth - mDividerInsets * 2; mRootBounds.set(configuration.windowConfiguration.getBounds()); - mDividerSnapAlgorithm = getSnapAlgorithm(context.getResources(), mRootBounds); + mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); resetDividerPosition(); } @@ -114,7 +115,7 @@ public class SplitLayout { mContext = mContext.createConfigurationContext(configuration); mSplitWindowManager.setConfiguration(configuration); mRootBounds.set(rootBounds); - mDividerSnapAlgorithm = getSnapAlgorithm(mContext.getResources(), mRootBounds); + mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); resetDividerPosition(); // Don't inflate divider bar if it is not initialized. @@ -217,15 +218,15 @@ public class SplitLayout { return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss); } - private DividerSnapAlgorithm getSnapAlgorithm(Resources resources, Rect rootBounds) { + private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) { final boolean isLandscape = isLandscape(rootBounds); return new DividerSnapAlgorithm( - resources, + context.getResources(), rootBounds.width(), rootBounds.height(), mDividerSize, !isLandscape, - new Rect() /* insets */, + getDisplayInsets(context), isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */); } @@ -250,6 +251,15 @@ public class SplitLayout { animator.start(); } + private static Rect getDisplayInsets(Context context) { + return context.getSystemService(WindowManager.class) + .getMaximumWindowMetrics() + .getWindowInsets() + .getInsets(WindowInsets.Type.navigationBars() + | WindowInsets.Type.statusBars() + | WindowInsets.Type.displayCutout()).toRect(); + } + private static boolean isLandscape(Rect bounds) { return bounds.width() > bounds.height(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 17709438baba..58bf22ad29b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -85,7 +85,8 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange @Override public void onDisplayAdded(int displayId) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display added: %d", displayId); - final Context context = mDisplayController.getDisplayContext(displayId); + final Context context = mDisplayController.getDisplayContext(displayId) + .createWindowContext(TYPE_APPLICATION_OVERLAY, null); final WindowManager wm = context.getSystemService(WindowManager.class); // TODO(b/169894807): Figure out the right layer for this, needs to be below the task bar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index aab2334f8255..9a09ca43d1d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -235,7 +235,7 @@ public class DragAndDropPolicy { mStarter.startShortcut(packageName, id, stage, position, opts, user); } else { mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), - mContext, null, stage, position, opts); + null, stage, position, opts); } } @@ -295,7 +295,7 @@ public class DragAndDropPolicy { @Nullable Bundle options); void startShortcut(String packageName, String shortcutId, @StageType int stage, @StagePosition int position, @Nullable Bundle options, UserHandle user); - void startIntent(PendingIntent intent, Context context, Intent fillInIntent, + void startIntent(PendingIntent intent, Intent fillInIntent, @StageType int stage, @StagePosition int position, @Nullable Bundle options); void enterSplitScreen(int taskId, boolean leftOrTop); @@ -337,9 +337,8 @@ public class DragAndDropPolicy { } @Override - public void startIntent(PendingIntent intent, Context context, - @Nullable Intent fillInIntent, int stage, int position, - @Nullable Bundle options) { + public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, int stage, + int position, @Nullable Bundle options) { try { intent.send(mContext, 0, fillInIntent, null, null, null, options); } catch (PendingIntent.CanceledException e) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java index f4c0f9384705..cf35656a395b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java @@ -67,7 +67,7 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { final SurfaceSession mSurfaceSession = new SurfaceSession(); - private final SplitScreenTransitions mSplitTransitions; + private final LegacySplitScreenTransitions mSplitTransitions; LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController, ShellTaskOrganizer shellTaskOrganizer, @@ -75,7 +75,7 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { SyncTransactionQueue syncQueue) { mSplitScreenController = splitScreenController; mTaskOrganizer = shellTaskOrganizer; - mSplitTransitions = new SplitScreenTransitions(splitScreenController.mTransactionPool, + mSplitTransitions = new LegacySplitScreenTransitions(splitScreenController.mTransactionPool, transitions, mSplitScreenController, this); transitions.addHandler(mSplitTransitions); mSyncQueue = syncQueue; @@ -112,7 +112,7 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { return mTaskOrganizer; } - SplitScreenTransitions getSplitTransitions() { + LegacySplitScreenTransitions getSplitTransitions() { return mSplitTransitions; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java index eea5c08818cc..27c56fd55e40 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java @@ -30,6 +30,7 @@ import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.app.WindowConfiguration; import android.graphics.Rect; import android.os.IBinder; @@ -46,7 +47,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; /** Plays transition animations for split-screen */ -public class SplitScreenTransitions implements Transitions.TransitionHandler { +public class LegacySplitScreenTransitions implements Transitions.TransitionHandler { private static final String TAG = "SplitScreenTransitions"; public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10; @@ -67,7 +68,7 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler { private Transitions.TransitionFinishCallback mFinishCallback = null; private SurfaceControl.Transaction mFinishTransaction; - SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, + LegacySplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, @NonNull LegacySplitScreenController splitScreen, @NonNull LegacySplitScreenTaskListener listener) { mTransactionPool = pool; @@ -91,9 +92,11 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler { // is nothing behind it. ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK) && triggerTask.parentTaskId == mListener.mPrimary.taskId) - // if a non-resizable is launched, we also need to leave split-screen. + // if a non-resizable is launched when it is not supported in multi window, + // we also need to leave split-screen. || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) - && !triggerTask.isResizeable); + && !triggerTask.isResizeable + && !ActivityTaskManager.supportsNonResizableMultiWindow()); // In both cases, dismiss the primary if (shouldDismiss) { WindowManagerProxy.buildDismissSplit(out, mListener, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java index 82468ad999b4..5a2ef568d82a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java @@ -46,6 +46,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; +import java.util.function.BooleanSupplier; /** * Proxy to simplify calls into window manager/activity manager @@ -208,11 +209,17 @@ class WindowManagerProxy { return false; } ActivityManager.RunningTaskInfo topHomeTask = null; + // One-time lazy wrapper to avoid duplicated IPC in loop. Not store as class variable + // because the value can be changed at runtime. + final BooleanSupplier supportsNonResizableMultiWindow = + createSupportsNonResizableMultiWindowSupplier(); for (int i = rootTasks.size() - 1; i >= 0; --i) { final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i); - // Only move resizeable task to split secondary. However, we have an exception - // for non-resizable home because we will minimize to show it. - if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME) { + // Check whether to move resizeable task to split secondary. + // Also, we have an exception for non-resizable home because we will minimize to show + // it. + if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME + && !supportsNonResizableMultiWindow.getAsBoolean()) { continue; } // Only move fullscreen tasks to split secondary. @@ -357,6 +364,21 @@ class WindowManagerProxy { outWct.setFocusable(tiles.mPrimary.token, true /* focusable */); } + /** Creates a lazy wrapper to get whether it supports non-resizable in multi window. */ + private static BooleanSupplier createSupportsNonResizableMultiWindowSupplier() { + return new BooleanSupplier() { + private Boolean mSupportsNonResizableMultiWindow; + @Override + public boolean getAsBoolean() { + if (mSupportsNonResizableMultiWindow == null) { + mSupportsNonResizableMultiWindow = + ActivityTaskManager.supportsNonResizableMultiWindow(); + } + return mSupportsNonResizableMultiWindow; + } + }; + } + /** * Utility to apply a sync transaction serially with other sync transactions. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl new file mode 100644 index 000000000000..008b5087d7da --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.onehanded; + +/** + * Interface that is exposed to remote callers to manipulate the OneHanded feature. + */ +interface IOneHanded { + + /** + * Enters one handed mode. + */ + oneway void startOneHanded() = 1; + + /** + * Exits one handed mode. + */ + oneway void stopOneHanded() = 2; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java index 4f31c370108d..242f8f120e27 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java @@ -26,6 +26,14 @@ import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEv */ @ExternalThread public interface OneHanded { + + /** + * Returns a binder that can be passed to an external process to manipulate OneHanded. + */ + default IOneHanded createExternalInterface() { + return null; + } + /** * Return one handed settings enabled or not. */ @@ -81,4 +89,9 @@ public interface OneHanded { * Receive onConfigurationChanged() events */ void onConfigChanged(Configuration newConfig); + + /** + * Notifies when user switch complete + */ + void onUserSwitch(int userId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java index d90cc4769286..703eba9d6af7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java @@ -17,13 +17,12 @@ package com.android.wm.shell.onehanded; import android.content.Context; -import android.content.res.Resources; +import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.Log; import android.view.SurfaceControl; import android.view.SurfaceSession; -import android.view.WindowManager; import android.window.DisplayAreaAppearedInfo; import android.window.DisplayAreaInfo; import android.window.DisplayAreaOrganizer; @@ -31,11 +30,13 @@ import android.window.DisplayAreaOrganizer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.core.content.ContextCompat; import com.android.internal.annotations.GuardedBy; import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; +import java.io.PrintWriter; import java.util.List; import java.util.concurrent.Executor; @@ -50,14 +51,16 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer private final Object mLock = new Object(); private final SurfaceSession mSurfaceSession = new SurfaceSession(); - private final float[] mColor; - private final float mAlpha; - private final Rect mRect; + private final float[] mDefaultColor; private final Executor mMainExecutor; - private final Rect mDisplaySize; private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; + /** + * The background to distinguish the boundary of translated windows and empty region when + * one handed mode triggered. + */ + private Rect mBkgBounds; @VisibleForTesting @GuardedBy("mLock") boolean mIsShowing; @@ -82,15 +85,18 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer mMainExecutor.execute(() -> removeBackgroundPanelLayer()); } - public OneHandedBackgroundPanelOrganizer(Context context, WindowManager windowManager, - DisplayController displayController, Executor executor) { + public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout, + Executor executor) { super(executor); - mDisplaySize = windowManager.getCurrentWindowMetrics().getBounds(); - final Resources res = context.getResources(); - final float defaultRGB = res.getFloat(R.dimen.config_one_handed_background_rgb); - mColor = new float[]{defaultRGB, defaultRGB, defaultRGB}; - mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha); - mRect = new Rect(0, 0, mDisplaySize.width(), mDisplaySize.height()); + // Ensure the mBkgBounds is portrait, due to OHM only support on portrait + if (displayLayout.height() > displayLayout.width()) { + mBkgBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height()); + } else { + mBkgBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width()); + } + final int defaultColor = ContextCompat.getColor(context, R.color.GM2_grey_800); + mDefaultColor = new float[]{Color.red(defaultColor) / 255.0f, + Color.green(defaultColor) / 255.0f, Color.blue(defaultColor) / 255.0f}; mMainExecutor = executor; mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; } @@ -144,9 +150,10 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer if (mBackgroundSurface == null) { mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession) .setParent(mParentLeash) + .setBufferSize(mBkgBounds.width(), mBkgBounds.height()) .setColorLayer() - .setFormat(PixelFormat.RGBA_8888) - .setOpaque(false) + .setFormat(PixelFormat.RGB_888) + .setOpaque(true) .setName("one-handed-background-panel") .setCallsite("OneHandedBackgroundPanelOrganizer") .build(); @@ -170,8 +177,7 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer SurfaceControl.Transaction transaction = mSurfaceControlTransactionFactory.getTransaction(); transaction.setLayer(mBackgroundSurface, -1 /* at bottom-most layer */) - .setColor(mBackgroundSurface, mColor) - .setAlpha(mBackgroundSurface, mAlpha) + .setColor(mBackgroundSurface, mDefaultColor) .show(mBackgroundSurface) .apply(); transaction.close(); @@ -188,11 +194,21 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer SurfaceControl.Transaction transaction = mSurfaceControlTransactionFactory.getTransaction(); - transaction.remove(mBackgroundSurface); - transaction.apply(); + transaction.remove(mBackgroundSurface).apply(); transaction.close(); mBackgroundSurface = null; mIsShowing = false; } } + + void dump(@NonNull PrintWriter pw) { + final String innerPrefix = " "; + pw.println(TAG + "states: "); + pw.print(innerPrefix + "mIsShowing="); + pw.println(mIsShowing); + pw.print(innerPrefix + "mBkgBounds="); + pw.println(mBkgBounds); + pw.print(innerPrefix + "mDefaultColor="); + pw.println(mDefaultColor); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 5fc7c987899f..38cf9e6183b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -17,20 +17,25 @@ package com.android.wm.shell.onehanded; import static android.os.UserHandle.USER_CURRENT; +import static android.os.UserHandle.myUserId; +import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + +import android.annotation.BinderThread; import android.content.ComponentName; import android.content.Context; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; import android.content.res.Configuration; import android.database.ContentObserver; -import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.Settings; import android.util.Slog; +import android.view.Surface; import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -43,6 +48,8 @@ import com.android.internal.logging.UiEventLogger; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -54,7 +61,7 @@ import java.io.PrintWriter; /** * Manages and manipulates the one handed states, transitions, and gesture for phones. */ -public class OneHandedController { +public class OneHandedController implements RemoteCallable<OneHandedController> { private static final String TAG = "OneHandedController"; private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = @@ -68,11 +75,14 @@ public class OneHandedController { private volatile boolean mIsSwipeToNotificationEnabled; private boolean mTaskChangeToExit; private boolean mLockedDisabled; + private int mUserId; private float mOffSetFraction; - private final Context mContext; + private Context mContext; + + private final AccessibilityManager mAccessibilityManager; private final DisplayController mDisplayController; - private final OneHandedGestureHandler mGestureHandler; + private final OneHandedSettingsUtil mOneHandedSettingsUtil; private final OneHandedTimeoutHandler mTimeoutHandler; private final OneHandedTouchHandler mTouchHandler; private final OneHandedTutorialHandler mTutorialHandler; @@ -82,10 +92,9 @@ public class OneHandedController { private final ShellExecutor mMainExecutor; private final Handler mMainHandler; private final OneHandedImpl mImpl = new OneHandedImpl(); - private final WindowManager mWindowManager; private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer; - private final AccessibilityManager mAccessibilityManager; + private OneHandedGestureHandler mGestureHandler; private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; /** @@ -93,8 +102,29 @@ public class OneHandedController { */ private final DisplayChangeController.OnDisplayChangingListener mRotationController = (display, fromRotation, toRotation, wct) -> { - if (mDisplayAreaOrganizer != null) { - mDisplayAreaOrganizer.onRotateDisplay(fromRotation, toRotation, wct); + if (!isInitialized()) { + return; + } + mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct); + mGestureHandler.onRotateDisplay(mDisplayAreaOrganizer.getDisplayLayout()); + }; + + private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener = + new DisplayController.OnDisplaysChangedListener() { + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + if (displayId != DEFAULT_DISPLAY || !isInitialized()) { + return; + } + updateDisplayLayout(displayId); + } + + @Override + public void onDisplayAdded(int displayId) { + if (displayId != DEFAULT_DISPLAY || !isInitialized()) { + return; + } + updateDisplayLayout(displayId); } }; @@ -108,17 +138,22 @@ public class OneHandedController { new AccessibilityManager.AccessibilityStateChangeListener() { @Override public void onAccessibilityStateChanged(boolean enabled) { + if (!isInitialized()) { + return; + } if (enabled) { - final int mOneHandedTimeout = OneHandedSettingsUtil - .getSettingsOneHandedModeTimeout(mContext.getContentResolver()); + final int mOneHandedTimeout = mOneHandedSettingsUtil + .getSettingsOneHandedModeTimeout( + mContext.getContentResolver(), mUserId); final int timeout = mAccessibilityManager .getRecommendedTimeoutMillis(mOneHandedTimeout * 1000 /* align with A11y timeout millis */, AccessibilityManager.FLAG_CONTENT_CONTROLS); mTimeoutHandler.setTimeout(timeout / 1000); } else { - mTimeoutHandler.setTimeout(OneHandedSettingsUtil - .getSettingsOneHandedModeTimeout(mContext.getContentResolver())); + mTimeoutHandler.setTimeout(mOneHandedSettingsUtil + .getSettingsOneHandedModeTimeout( + mContext.getContentResolver(), mUserId)); } } }; @@ -136,6 +171,14 @@ public class OneHandedController { } }; + private boolean isInitialized() { + if (mDisplayAreaOrganizer == null || mDisplayController == null + || mGestureHandler == null || mOneHandedSettingsUtil == null) { + Slog.w(TAG, "Components may not initialized yet!"); + return false; + } + return true; + } /** * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported. @@ -143,8 +186,8 @@ public class OneHandedController { @Nullable public static OneHandedController create( Context context, WindowManager windowManager, DisplayController displayController, - TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, - ShellExecutor mainExecutor, Handler mainHandler) { + DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, + UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) { if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) { Slog.w(TAG, "Device doesn't support OneHanded feature"); return null; @@ -158,32 +201,31 @@ public class OneHandedController { OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler, mainExecutor); OneHandedGestureHandler gestureHandler = new OneHandedGestureHandler( - context, windowManager, displayController, ViewConfiguration.get(context), - mainExecutor); + context, displayLayout, ViewConfiguration.get(context), mainExecutor); OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer = - new OneHandedBackgroundPanelOrganizer(context, windowManager, displayController, - mainExecutor); + new OneHandedBackgroundPanelOrganizer(context, displayLayout, mainExecutor); OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer( - context, windowManager, displayController, animationController, tutorialHandler, + context, displayLayout, animationController, tutorialHandler, oneHandedBackgroundPanelOrganizer, mainExecutor); + OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil(); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); IOverlayManager overlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); - return new OneHandedController(context, windowManager, displayController, + return new OneHandedController(context, displayController, oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler, - gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager, - taskStackListener, mainExecutor, mainHandler); + gestureHandler, settingsUtil, timeoutHandler, oneHandedUiEventsLogger, + overlayManager, taskStackListener, mainExecutor, mainHandler); } @VisibleForTesting OneHandedController(Context context, - WindowManager windowManager, DisplayController displayController, OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer, OneHandedDisplayAreaOrganizer displayAreaOrganizer, OneHandedTouchHandler touchHandler, OneHandedTutorialHandler tutorialHandler, OneHandedGestureHandler gestureHandler, + OneHandedSettingsUtil settingsUtil, OneHandedTimeoutHandler timeoutHandler, OneHandedUiEventLogger uiEventsLogger, IOverlayManager overlayManager, @@ -191,7 +233,7 @@ public class OneHandedController { ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; - mWindowManager = windowManager; + mOneHandedSettingsUtil = settingsUtil; mBackgroundPanelOrganizer = backgroundPanelOrganizer; mDisplayAreaOrganizer = displayAreaOrganizer; mDisplayController = displayController; @@ -204,16 +246,18 @@ public class OneHandedController { mOneHandedUiEventLogger = uiEventsLogger; mTaskStackListener = taskStackListener; + mDisplayController.addDisplayWindowListener(mDisplaysChangedListener); final float offsetPercentageConfig = context.getResources().getFraction( R.fraction.config_one_handed_offset, 1, 1); final int sysPropPercentageConfig = SystemProperties.getInt( ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f)); + mUserId = myUserId(); mOffSetFraction = sysPropPercentageConfig / 100.0f; - mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - context.getContentResolver()); + mIsOneHandedEnabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( + context.getContentResolver(), mUserId); mIsSwipeToNotificationEnabled = - OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - context.getContentResolver()); + mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( + context.getContentResolver(), mUserId); mTimeoutHandler = timeoutHandler; mEnabledObserver = getObserver(this::onEnabledSettingChanged); @@ -223,9 +267,8 @@ public class OneHandedController { getObserver(this::onSwipeToNotificationEnabledSettingChanged); mDisplayController.addDisplayChangingController(mRotationController); - setupCallback(); - setupSettingObservers(); + registerSettingObservers(mUserId); setupTimeoutListener(); setupGesturalOverlay(); updateSettings(); @@ -239,6 +282,16 @@ public class OneHandedController { return mImpl; } + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + /** * Set one handed enabled or disabled when user update settings */ @@ -273,8 +326,14 @@ public class OneHandedController { Slog.d(TAG, "Temporary lock disabled"); return; } + final int currentRotation = mDisplayAreaOrganizer.getDisplayLayout().rotation(); + if (currentRotation != Surface.ROTATION_0 && currentRotation != Surface.ROTATION_180) { + Slog.w(TAG, "One handed mode only support portrait mode"); + return; + } if (!mDisplayAreaOrganizer.isInOneHanded()) { - final int yOffSet = Math.round(getDisplaySize().height() * mOffSetFraction); + final int yOffSet = Math.round( + mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction); mDisplayAreaOrganizer.scheduleOffset(0, yOffSet); mTimeoutHandler.resetTimer(); @@ -324,27 +383,44 @@ public class OneHandedController { } } - private void setupSettingObservers() { - OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED, - mContext.getContentResolver(), mEnabledObserver); - OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT, - mContext.getContentResolver(), mTimeoutObserver); - OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT, - mContext.getContentResolver(), mTaskChangeExitObserver); - OneHandedSettingsUtil.registerSettingsKeyObserver( + private void registerSettingObservers(int newUserId) { + mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED, + mContext.getContentResolver(), mEnabledObserver, newUserId); + mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT, + mContext.getContentResolver(), mTimeoutObserver, newUserId); + mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT, + mContext.getContentResolver(), mTaskChangeExitObserver, newUserId); + mOneHandedSettingsUtil.registerSettingsKeyObserver( Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, - mContext.getContentResolver(), mSwipeToNotificationEnabledObserver); + mContext.getContentResolver(), mSwipeToNotificationEnabledObserver, newUserId); + } + + private void unregisterSettingObservers() { + mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(), + mEnabledObserver); + mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(), + mTimeoutObserver); + mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(), + mTaskChangeExitObserver); + mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(), + mSwipeToNotificationEnabledObserver); } private void updateSettings() { - setOneHandedEnabled(OneHandedSettingsUtil - .getSettingsOneHandedModeEnabled(mContext.getContentResolver())); - mTimeoutHandler.setTimeout(OneHandedSettingsUtil - .getSettingsOneHandedModeTimeout(mContext.getContentResolver())); - setTaskChangeToExit(OneHandedSettingsUtil - .getSettingsTapsAppToExit(mContext.getContentResolver())); - setSwipeToNotificationEnabled(OneHandedSettingsUtil - .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver())); + setOneHandedEnabled(mOneHandedSettingsUtil + .getSettingsOneHandedModeEnabled(mContext.getContentResolver(), mUserId)); + mTimeoutHandler.setTimeout(mOneHandedSettingsUtil + .getSettingsOneHandedModeTimeout(mContext.getContentResolver(), mUserId)); + setTaskChangeToExit(mOneHandedSettingsUtil + .getSettingsTapsAppToExit(mContext.getContentResolver(), mUserId)); + setSwipeToNotificationEnabled(mOneHandedSettingsUtil + .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver(), mUserId)); + } + + private void updateDisplayLayout(int displayId) { + final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId); + mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout); + mGestureHandler.onDisplayChanged(newDisplayLayout); } private ContentObserver getObserver(Runnable onChangeRunnable) { @@ -358,8 +434,8 @@ public class OneHandedController { @VisibleForTesting void onEnabledSettingChanged() { - final boolean enabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - mContext.getContentResolver()); + final boolean enabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( + mContext.getContentResolver(), mUserId); mOneHandedUiEventLogger.writeEvent(enabled ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_ON : OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_OFF); @@ -368,14 +444,14 @@ public class OneHandedController { // Also checks swipe to notification settings since they all need gesture overlay. setEnabledGesturalOverlay( - enabled || OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - mContext.getContentResolver())); + enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( + mContext.getContentResolver(), mUserId)); } @VisibleForTesting void onTimeoutSettingChanged() { - final int newTimeout = OneHandedSettingsUtil.getSettingsOneHandedModeTimeout( - mContext.getContentResolver()); + final int newTimeout = mOneHandedSettingsUtil.getSettingsOneHandedModeTimeout( + mContext.getContentResolver(), mUserId); int metricsId = OneHandedUiEventLogger.OneHandedSettingsTogglesEvent.INVALID.getId(); switch (newTimeout) { case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER: @@ -403,8 +479,8 @@ public class OneHandedController { @VisibleForTesting void onTaskChangeExitSettingChanged() { - final boolean enabled = OneHandedSettingsUtil.getSettingsTapsAppToExit( - mContext.getContentResolver()); + final boolean enabled = mOneHandedSettingsUtil.getSettingsTapsAppToExit( + mContext.getContentResolver(), mUserId); mOneHandedUiEventLogger.writeEvent(enabled ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_ON : OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_OFF); @@ -415,14 +491,14 @@ public class OneHandedController { @VisibleForTesting void onSwipeToNotificationEnabledSettingChanged() { final boolean enabled = - OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - mContext.getContentResolver()); + mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( + mContext.getContentResolver(), mUserId); setSwipeToNotificationEnabled(enabled); // Also checks one handed mode settings since they all need gesture overlay. setEnabledGesturalOverlay( - enabled || OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - mContext.getContentResolver())); + enabled || mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( + mContext.getContentResolver(), mUserId)); } private void setupTimeoutListener() { @@ -430,24 +506,6 @@ public class OneHandedController { OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_TIMEOUT_OUT)); } - /** - * Query the current display real size from {@link WindowManager} - * - * @return {@link WindowManager#getCurrentWindowMetrics()#getBounds()} - */ - private Rect getDisplaySize() { - if (mWindowManager == null) { - Slog.e(TAG, "WindowManager instance is null! Can not get display size!"); - return new Rect(); - } - final Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds(); - if (displaySize.width() == 0 || displaySize.height() == 0) { - Slog.e(TAG, "Display size error! width = " + displaySize.width() - + ", height = " + displaySize.height()); - } - return displaySize; - } - @VisibleForTesting boolean isLockedDisabled() { return mLockedDisabled; @@ -459,7 +517,7 @@ public class OneHandedController { } mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled); - mGestureHandler.onOneHandedEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled); + mGestureHandler.onGestureEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled); if (!mIsOneHandedEnabled) { mDisplayAreaOrganizer.unregisterOrganizer(); @@ -480,7 +538,8 @@ public class OneHandedController { } private void setupGesturalOverlay() { - if (!OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver())) { + if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( + mContext.getContentResolver(), mUserId)) { return; } @@ -507,10 +566,15 @@ public class OneHandedController { @VisibleForTesting void setLockedDisabled(boolean locked, boolean enabled) { - if (enabled == mIsOneHandedEnabled) { + final boolean isFeatureEnabled = mIsOneHandedEnabled || mIsSwipeToNotificationEnabled; + + if (enabled == isFeatureEnabled) { return; } mLockedDisabled = locked && !enabled; + + // Disabled gesture when keyguard ON + mGestureHandler.onGestureEnabled(!mLockedDisabled && isFeatureEnabled); } private void onConfigChanged(Configuration newConfig) { @@ -523,6 +587,14 @@ public class OneHandedController { } } + private void onUserSwitch(int newUserId) { + unregisterSettingObservers(); + mUserId = newUserId; + registerSettingObservers(newUserId); + updateSettings(); + updateOneHandedEnabled(); + } + public void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG + "States: "); @@ -530,6 +602,12 @@ public class OneHandedController { pw.println(mOffSetFraction); pw.print(innerPrefix + "mLockedDisabled="); pw.println(mLockedDisabled); + pw.print(innerPrefix + "mUserId="); + pw.println(mUserId); + + if (mBackgroundPanelOrganizer != null) { + mBackgroundPanelOrganizer.dump(pw); + } if (mDisplayAreaOrganizer != null) { mDisplayAreaOrganizer.dump(pw); @@ -551,7 +629,7 @@ public class OneHandedController { mTutorialHandler.dump(pw); } - OneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver()); + mOneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver(), mUserId); if (mOverlayManager != null) { OverlayInfo info = null; @@ -567,8 +645,22 @@ public class OneHandedController { } } + /** + * The interface for calls from outside the Shell, within the host process. + */ @ExternalThread private class OneHandedImpl implements OneHanded { + private IOneHandedImpl mIOneHanded; + + @Override + public IOneHanded createExternalInterface() { + if (mIOneHanded != null) { + mIOneHanded.invalidate(); + } + mIOneHanded = new IOneHandedImpl(OneHandedController.this); + return mIOneHanded; + } + @Override public boolean isOneHandedEnabled() { // This is volatile so return directly @@ -636,5 +728,47 @@ public class OneHandedController { OneHandedController.this.onConfigChanged(newConfig); }); } + + @Override + public void onUserSwitch(int userId) { + mMainExecutor.execute(() -> { + OneHandedController.this.onUserSwitch(userId); + }); + } + } + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IOneHandedImpl extends IOneHanded.Stub { + private OneHandedController mController; + + IOneHandedImpl(OneHandedController controller) { + mController = controller; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; + } + + @Override + public void startOneHanded() { + executeRemoteCallWithTaskPermission(mController, "startOneHanded", + (controller) -> { + controller.startOneHanded(); + }); + } + + @Override + public void stopOneHanded() { + executeRemoteCallWithTaskPermission(mController, "stopOneHanded", + (controller) -> { + controller.stopOneHanded(); + }); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index 0238fa8a7936..682c9a3f0d62 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -23,9 +23,7 @@ import android.content.Context; import android.graphics.Rect; import android.os.SystemProperties; import android.util.ArrayMap; -import android.util.Slog; import android.view.SurfaceControl; -import android.view.WindowManager; import android.window.DisplayAreaAppearedInfo; import android.window.DisplayAreaInfo; import android.window.DisplayAreaOrganizer; @@ -37,7 +35,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import java.io.PrintWriter; @@ -59,7 +57,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { private static final String ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION = "persist.debug.one_handed_translate_animation_duration"; - private final WindowManager mWindowManager; + private DisplayLayout mDisplayLayout = new DisplayLayout(); + private final Rect mLastVisualDisplayBounds = new Rect(); private final Rect mDefaultDisplayBounds = new Rect(); @@ -67,7 +66,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { private int mEnterExitAnimationDurationMs; private ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = new ArrayMap(); - private DisplayController mDisplayController; private OneHandedAnimationController mAnimationController; private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; @@ -110,17 +108,15 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { * Constructor of OneHandedDisplayAreaOrganizer */ public OneHandedDisplayAreaOrganizer(Context context, - WindowManager windowManager, - DisplayController displayController, + DisplayLayout displayLayout, OneHandedAnimationController animationController, OneHandedTutorialHandler tutorialHandler, OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer, ShellExecutor mainExecutor) { super(mainExecutor); - mWindowManager = windowManager; + mDisplayLayout.set(displayLayout); + updateDisplayBounds(); mAnimationController = animationController; - mDisplayController = displayController; - mLastVisualDisplayBounds.set(getDisplayBounds()); final int animationDurationConfig = context.getResources().getInteger( R.integer.config_one_handed_translate_animation_duration); mEnterExitAnimationDurationMs = @@ -150,7 +146,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { final DisplayAreaAppearedInfo info = displayAreaInfos.get(i); onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash()); } - mDefaultDisplayBounds.set(getDisplayBounds()); + updateDisplayBounds(); return displayAreaInfos; } @@ -161,25 +157,21 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { } /** - * Handler for display rotation changes by below policy which - * handles 90 degree display rotation changes {@link Surface.Rotation}. + * Handler for display rotation changes by {@link DisplayLayout} * - * @param fromRotation starting rotation of the display. - * @param toRotation target rotation of the display (after rotating). - * @param wct A task transaction {@link WindowContainerTransaction} from - * {@link DisplayChangeController} to populate. + * @param context Any context + * @param toRotation target rotation of the display (after rotating). + * @param wct A task transaction {@link WindowContainerTransaction} from + * {@link DisplayChangeController} to populate. */ - public void onRotateDisplay(int fromRotation, int toRotation, WindowContainerTransaction wct) { - // Stop one handed without animation and reset cropped size immediately - final Rect newBounds = new Rect(mDefaultDisplayBounds); - final boolean isOrientationDiff = Math.abs(fromRotation - toRotation) % 2 == 1; - - if (isOrientationDiff) { - resetWindowsOffset(wct); - mDefaultDisplayBounds.set(newBounds); - mLastVisualDisplayBounds.set(newBounds); - finishOffset(0, TRANSITION_DIRECTION_EXIT); + public void onRotateDisplay(Context context, int toRotation, WindowContainerTransaction wct) { + if (mDisplayLayout.rotation() == toRotation) { + return; } + mDisplayLayout.rotateTo(context.getResources(), toRotation); + resetWindowsOffset(wct); + updateDisplayBounds(); + finishOffset(0, TRANSITION_DIRECTION_EXIT); } /** @@ -191,9 +183,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mDefaultDisplayBounds.top + yOffset, mDefaultDisplayBounds.right, mDefaultDisplayBounds.bottom + yOffset); - final Rect fromBounds = getLastVisualDisplayBounds() != null - ? getLastVisualDisplayBounds() - : mDefaultDisplayBounds; + final Rect fromBounds = getLastVisualDisplayBounds(); final int direction = yOffset > 0 ? TRANSITION_DIRECTION_TRIGGER : TRANSITION_DIRECTION_EXIT; @@ -219,7 +209,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { applyTransaction(wct); } - private void resetWindowsOffset(WindowContainerTransaction wct) { + @VisibleForTesting + void resetWindowsOffset(WindowContainerTransaction wct) { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mDisplayAreaTokenMap.forEach( @@ -292,18 +283,19 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { return mLastVisualDisplayBounds; } + @VisibleForTesting @Nullable - private Rect getDisplayBounds() { - if (mWindowManager == null) { - Slog.e(TAG, "WindowManager instance is null! Can not get display size!"); - return new Rect(); - } - final Rect displayBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - if (displayBounds.width() == 0 || displayBounds.height() == 0) { - Slog.e(TAG, "Display size error! width = " + displayBounds.width() - + ", height = " + displayBounds.height()); - } - return displayBounds; + Rect getLastDisplayBounds() { + return mLastVisualDisplayBounds; + } + + public DisplayLayout getDisplayLayout() { + return mDisplayLayout; + } + + @VisibleForTesting + void setDisplayLayout(@NonNull DisplayLayout displayLayout) { + mDisplayLayout.set(displayLayout); } @VisibleForTesting @@ -311,6 +303,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { return mDisplayAreaTokenMap; } + void updateDisplayBounds() { + mDefaultDisplayBounds.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height()); + mLastVisualDisplayBounds.set(mDefaultDisplayBounds); + } + /** * Register transition callback */ @@ -323,13 +320,13 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { pw.println(TAG + "states: "); pw.print(innerPrefix + "mIsInOneHanded="); pw.println(mIsInOneHanded); + pw.print(innerPrefix + "mDisplayLayout.rotation()="); + pw.println(mDisplayLayout.rotation()); pw.print(innerPrefix + "mDisplayAreaTokenMap="); pw.println(mDisplayAreaTokenMap); pw.print(innerPrefix + "mDefaultDisplayBounds="); pw.println(mDefaultDisplayBounds); pw.print(innerPrefix + "mLastVisualDisplayBounds="); pw.println(mLastVisualDisplayBounds); - pw.print(innerPrefix + "getDisplayBounds()="); - pw.println(getDisplayBounds()); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java index 778876c76afe..9e83a61667b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java @@ -24,7 +24,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Looper; -import android.util.Log; import android.view.Display; import android.view.InputChannel; import android.view.InputEvent; @@ -33,29 +32,23 @@ import android.view.InputMonitor; import android.view.MotionEvent; import android.view.Surface; import android.view.ViewConfiguration; -import android.view.WindowManager; -import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayChangeController; -import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import java.io.PrintWriter; /** - * The class manage swipe up and down gesture for 3-Button mode navigation, - * others(e.g, 2-button, full gesture mode) are handled by Launcher quick steps. - * TODO(b/160934654) Migrate to Launcher quick steps + * The class manage swipe up and down gesture for 3-Button mode navigation, others(e.g, 2-button, + * full gesture mode) are handled by Launcher quick steps. TODO(b/160934654) Migrate to Launcher + * quick steps */ -public class OneHandedGestureHandler implements OneHandedTransitionCallback, - DisplayChangeController.OnDisplayChangingListener { +public class OneHandedGestureHandler implements OneHandedTransitionCallback { private static final String TAG = "OneHandedGestureHandler"; - private static final boolean DEBUG_GESTURE = false; private static final int ANGLE_MAX = 150; private static final int ANGLE_MIN = 30; @@ -64,7 +57,6 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); private final PointF mStartDragPos = new PointF(); - private final WindowManager mWindowManager; private boolean mPassedSlop; private boolean mAllowGesture; @@ -85,37 +77,35 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, private boolean mIsStopGesture; /** - * Constructor of OneHandedGestureHandler, we only handle the gesture of - * {@link Display#DEFAULT_DISPLAY} + * Constructor of OneHandedGestureHandler, we only handle the gesture of {@link + * Display#DEFAULT_DISPLAY} * - * @param context {@link Context} - * @param displayController {@link DisplayController} + * @param context Any context + * @param displayLayout Current {@link DisplayLayout} from controller + * @param viewConfig {@link ViewConfiguration} to obtain touch slop + * @param mainExecutor The wm-shell main executor */ - public OneHandedGestureHandler(Context context, WindowManager windowManager, - DisplayController displayController, ViewConfiguration viewConfig, + public OneHandedGestureHandler(Context context, + DisplayLayout displayLayout, + ViewConfiguration viewConfig, ShellExecutor mainExecutor) { - mWindowManager = windowManager; mMainExecutor = mainExecutor; - displayController.addDisplayChangingController(this); - mNavGestureHeight = getNavBarSize(context, - displayController.getDisplayLayout(DEFAULT_DISPLAY)); mDragDistThreshold = context.getResources().getDimensionPixelSize( R.dimen.gestures_onehanded_drag_threshold); + final float slop = viewConfig.getScaledTouchSlop(); mSquaredSlop = slop * slop; - + onDisplayChanged(displayLayout); updateIsEnabled(); } /** - * Notified by {@link OneHandedController}, when user update settings of Enabled or Disabled + * Notifies by {@link OneHandedController}, when swipe down gesture is enabled on 3 button + * navigation bar mode. * - * @param isEnabled is one handed settings enabled or not + * @param isEnabled Either one handed mode or swipe for notification function enabled or not */ - public void onOneHandedEnabled(boolean isEnabled) { - if (DEBUG_GESTURE) { - Log.d(TAG, "onOneHandedEnabled, isEnabled = " + isEnabled); - } + public void onGestureEnabled(boolean isEnabled) { mIsEnabled = isEnabled; updateIsEnabled(); } @@ -126,25 +116,31 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, } /** - * Register {@link OneHandedGestureEventCallback} to receive onStart(), onStop() callback + * Registers {@link OneHandedGestureEventCallback} to receive onStart(), onStop() callback */ public void setGestureEventListener(OneHandedGestureEventCallback callback) { mGestureEventCallback = callback; } + /** + * Called when onDisplayAdded() or onDisplayRemoved() callback + * @param displayLayout The latest {@link DisplayLayout} representing current displayId + */ + public void onDisplayChanged(DisplayLayout displayLayout) { + mNavGestureHeight = getNavBarSize(displayLayout); + mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(), + displayLayout.height()); + mRotation = displayLayout.rotation(); + } + private void onMotionEvent(MotionEvent ev) { int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { - mAllowGesture = isWithinTouchRegion(ev.getX(), ev.getY()) - && mRotation == Surface.ROTATION_0; + mAllowGesture = isWithinTouchRegion(ev.getX(), ev.getY()) && isGestureAvailable(); if (mAllowGesture) { mDownPos.set(ev.getX(), ev.getY()); mLastPos.set(mDownPos); } - if (DEBUG_GESTURE) { - Log.d(TAG, "ACTION_DOWN, mDownPos=" + mDownPos + ", mAllowGesture=" - + mAllowGesture); - } } else if (mAllowGesture) { switch (action) { case MotionEvent.ACTION_MOVE: @@ -204,34 +200,17 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, } private boolean isWithinTouchRegion(float x, float y) { - if (DEBUG_GESTURE) { - Log.d(TAG, "isWithinTouchRegion(), mGestureRegion=" + mGestureRegion + ", downX=" + x - + ", downY=" + y); - } return mGestureRegion.contains(Math.round(x), Math.round(y)); } - private int getNavBarSize(Context context, @Nullable DisplayLayout displayLayout) { - if (displayLayout != null) { - return displayLayout.navBarFrameHeight(); - } else { - return isRotated() - ? context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.navigation_bar_height_landscape) - : context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.navigation_bar_height); - } + private int getNavBarSize(@NonNull DisplayLayout displayLayout) { + return isGestureAvailable() ? displayLayout.navBarFrameHeight() : 0 /* In landscape */; } private void updateIsEnabled() { disposeInputChannel(); - // Either OHM or swipe notification shade can activate in portrait mode only - if (mIsEnabled && mIsThreeButtonModeEnabled && !isRotated()) { - final Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds(); - // Register input event receiver to monitor the touch region of NavBar gesture height - mGestureRegion.set(0, displaySize.height() - mNavGestureHeight, displaySize.width(), - displaySize.height()); + if (mIsEnabled && mIsThreeButtonModeEnabled && isGestureAvailable()) { mInputMonitor = InputManager.getInstance().monitorGestureInput( "onehanded-gesture-offset", DEFAULT_DISPLAY); try { @@ -251,10 +230,16 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, } } - @Override - public void onRotateDisplay(int displayId, int fromRotation, int toRotation, - WindowContainerTransaction t) { - mRotation = toRotation; + /** + * Handler for display rotation changes by {@link DisplayLayout} + * + * @param displayLayout The rotated displayLayout + */ + public void onRotateDisplay(DisplayLayout displayLayout) { + mRotation = displayLayout.rotation(); + mNavGestureHeight = getNavBarSize(displayLayout); + mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(), + displayLayout.height()); updateIsEnabled(); } @@ -270,8 +255,9 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, } } - private boolean isRotated() { - return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270; + private boolean isGestureAvailable() { + // Either OHM or swipe notification shade can activate in portrait mode only + return mRotation == Surface.ROTATION_0 || mRotation == Surface.ROTATION_180; } private boolean isValidStartAngle(float deltaX, float deltaY) { @@ -291,14 +277,18 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback, void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG + "States: "); + pw.print(innerPrefix + "mAllowGesture="); + pw.println(mAllowGesture); pw.print(innerPrefix + "mIsEnabled="); pw.println(mIsEnabled); + pw.print(innerPrefix + "mGestureRegion="); + pw.println(mGestureRegion); pw.print(innerPrefix + "mNavGestureHeight="); pw.println(mNavGestureHeight); pw.print(innerPrefix + "mIsThreeButtonModeEnabled="); pw.println(mIsThreeButtonModeEnabled); - pw.print(innerPrefix + "isLandscape="); - pw.println(isRotated()); + pw.print(innerPrefix + "mRotation="); + pw.println(mRotation); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java index f8217c64e53d..1b2fcdd6313e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java @@ -22,6 +22,8 @@ import android.database.ContentObserver; import android.net.Uri; import android.provider.Settings; +import androidx.annotation.Nullable; + import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -62,17 +64,19 @@ public final class OneHandedSettingsUtil { /** * Register one handed preference settings observer * - * @param key Setting key to monitor in observer - * @param resolver ContentResolver of context - * @param observer Observer from caller + * @param key Setting key to monitor in observer + * @param resolver ContentResolver of context + * @param observer Observer from caller + * @param newUserId New user id to be registered * @return uri key for observing */ - public static Uri registerSettingsKeyObserver(String key, ContentResolver resolver, - ContentObserver observer) { + @Nullable + public Uri registerSettingsKeyObserver(String key, ContentResolver resolver, + ContentObserver observer, int newUserId) { Uri uriKey = null; uriKey = Settings.Secure.getUriFor(key); if (resolver != null && uriKey != null) { - resolver.registerContentObserver(uriKey, false, observer); + resolver.registerContentObserver(uriKey, false, observer, newUserId); } return uriKey; } @@ -80,10 +84,10 @@ public final class OneHandedSettingsUtil { /** * Unregister one handed preference settings observer * - * @param resolver ContentResolver of context - * @param observer preference key change observer + * @param resolver ContentResolver of context + * @param observer preference key change observer */ - public static void unregisterSettingsKeyObserver(ContentResolver resolver, + public void unregisterSettingsKeyObserver(ContentResolver resolver, ContentObserver observer) { if (resolver != null) { resolver.unregisterContentObserver(observer); @@ -95,9 +99,9 @@ public final class OneHandedSettingsUtil { * * @return enable or disable one handed mode flag. */ - public static boolean getSettingsOneHandedModeEnabled(ContentResolver resolver) { - return Settings.Secure.getInt(resolver, - Settings.Secure.ONE_HANDED_MODE_ENABLED, 0 /* Disabled */) == 1; + public boolean getSettingsOneHandedModeEnabled(ContentResolver resolver, int userId) { + return Settings.Secure.getIntForUser(resolver, + Settings.Secure.ONE_HANDED_MODE_ENABLED, 0 /* Disabled */, userId) == 1; } /** @@ -105,40 +109,44 @@ public final class OneHandedSettingsUtil { * * @return enable or disable taps app exit. */ - public static boolean getSettingsTapsAppToExit(ContentResolver resolver) { - return Settings.Secure.getInt(resolver, - Settings.Secure.TAPS_APP_TO_EXIT, 0) == 1; + public boolean getSettingsTapsAppToExit(ContentResolver resolver, int userId) { + return Settings.Secure.getIntForUser(resolver, + Settings.Secure.TAPS_APP_TO_EXIT, 0, userId) == 1; } /** - * Query timeout value from Settings provider. - * Default is {@link OneHandedSettingsUtil#ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS} + * Query timeout value from Settings provider. Default is + * {@link OneHandedSettingsUtil#ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS} * * @return timeout value in seconds. */ - public static @OneHandedTimeout int getSettingsOneHandedModeTimeout(ContentResolver resolver) { - return Settings.Secure.getInt(resolver, - Settings.Secure.ONE_HANDED_MODE_TIMEOUT, ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS); + public @OneHandedTimeout int getSettingsOneHandedModeTimeout(ContentResolver resolver, + int userId) { + return Settings.Secure.getIntForUser(resolver, + Settings.Secure.ONE_HANDED_MODE_TIMEOUT, ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS, + userId); } /** * Returns whether swipe bottom to notification gesture enabled or not. */ - public static boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) { + public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver, int userId) { return Settings.Secure.getInt(resolver, - Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1) == 1; + Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0 /* Default OFF */) == 1; } - protected static void dump(PrintWriter pw, String prefix, ContentResolver resolver) { + void dump(PrintWriter pw, String prefix, ContentResolver resolver, + int userId) { final String innerPrefix = prefix + " "; - pw.println(prefix + TAG); + pw.println(innerPrefix + TAG); pw.print(innerPrefix + "isOneHandedModeEnable="); - pw.println(getSettingsOneHandedModeEnabled(resolver)); + pw.println(getSettingsOneHandedModeEnabled(resolver, userId)); pw.print(innerPrefix + "oneHandedTimeOut="); - pw.println(getSettingsOneHandedModeTimeout(resolver)); + pw.println(getSettingsOneHandedModeTimeout(resolver, userId)); pw.print(innerPrefix + "tapsAppToExit="); - pw.println(getSettingsTapsAppToExit(resolver)); + pw.println(getSettingsTapsAppToExit(resolver, userId)); } - private OneHandedSettingsUtil() {} + public OneHandedSettingsUtil() { + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl new file mode 100644 index 000000000000..a6ffa6e44584 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip; + +import android.app.PictureInPictureParams; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; + +import com.android.wm.shell.pip.IPipAnimationListener; + +/** + * Interface that is exposed to remote callers to manipulate the Pip feature. + */ +interface IPip { + + /** + * Notifies that Activity is about to be swiped to home with entering PiP transition and + * queries the destination bounds for PiP depends on Launcher's rotation and shelf height. + * + * @param componentName ComponentName represents the Activity + * @param activityInfo ActivityInfo tied to the Activity + * @param pictureInPictureParams PictureInPictureParams tied to the Activity + * @param launcherRotation Launcher rotation to calculate the PiP destination bounds + * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds + * @return destination bounds the PiP window should land into + */ + Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo, + in PictureInPictureParams pictureInPictureParams, + int launcherRotation, int shelfHeight) = 1; + + /** + * Notifies the swiping Activity to PiP onto home transition is finished + * + * @param componentName ComponentName represents the Activity + * @param destinationBounds the destination bounds the PiP window lands into + */ + oneway void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 2; + + /** + * Sets listener to get pinned stack animation callbacks. + */ + oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3; + + /** + * Sets the shelf height and visibility. + */ + oneway void setShelfHeight(boolean visible, int shelfHeight) = 4; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl new file mode 100644 index 000000000000..2569b780c1bb --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip; + +/** + * Listener interface that Launcher attaches to SystemUI to get Pip animation callbacks. + */ +oneway interface IPipAnimationListener { + /** + * Notifies the listener that the Pip animation is started. + */ + void onPipAnimationStarted(); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index d14c3e3c0dd4..6d4773bdeb1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -16,15 +16,10 @@ package com.android.wm.shell.pip; -import android.annotation.Nullable; -import android.app.PictureInPictureParams; -import android.content.ComponentName; -import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.pip.phone.PipTouchHandler; import java.io.PrintWriter; import java.util.function.Consumer; @@ -34,6 +29,14 @@ import java.util.function.Consumer; */ @ExternalThread public interface Pip { + + /** + * Returns a binder that can be passed to an external process to manipulate PIP. + */ + default IPip createExternalInterface() { + return null; + } + /** * Expand PIP, it's possible that specific request to activate the window via Alt-tab. */ @@ -109,30 +112,6 @@ public interface Pip { default void showPictureInPictureMenu() {} /** - * Called by Launcher when swiping an auto-pip enabled Activity to home starts - * @param componentName {@link ComponentName} represents the Activity entering PiP - * @param activityInfo {@link ActivityInfo} tied to the Activity - * @param pictureInPictureParams {@link PictureInPictureParams} tied to the Activity - * @param launcherRotation Rotation Launcher is in - * @param shelfHeight Shelf height when landing PiP window onto Launcher - * @return Destination bounds of PiP window based on the parameters passed in - */ - default Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, - int launcherRotation, int shelfHeight) { - return null; - } - - /** - * Called by Launcher when swiping an auto-pip enable Activity to home finishes - * @param componentName {@link ComponentName} represents the Activity entering PiP - * @param destinationBounds Destination bounds of PiP window - */ - default void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { - return; - } - - /** * Called by NavigationBar in order to listen in for PiP bounds change. This is mostly used * for times where the PiP bounds could conflict with SystemUI elements, such as a stashed * PiP and the Back-from-Edge gesture. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index a52db24aa184..af4ccadae538 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -540,9 +540,10 @@ public class PipAnimationController { // WindowContainerTransaction in task organizer final Rect destBounds = getDestinationBounds(); getSurfaceTransactionHelper().resetScale(tx, leash, destBounds); - if (transitionDirection == TRANSITION_DIRECTION_LEAVE_PIP) { - // Leaving to fullscreen, reset crop to null. - tx.setPosition(leash, destBounds.left, destBounds.top); + if (isOutPipDirection(transitionDirection)) { + // Exit pip, clear scale, position and crop. + tx.setMatrix(leash, 1, 0, 0, 1); + tx.setPosition(leash, 0, 0); tx.setWindowCrop(leash, 0, 0); } else { getSurfaceTransactionHelper().crop(tx, leash, destBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index cb39b4e63655..e3594d0cd367 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -35,6 +35,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; +import java.util.function.Consumer; /** * Singleton source of truth for the current state of PIP bounds. @@ -84,6 +85,7 @@ public final class PipBoundsState { private @Nullable Runnable mOnMinimalSizeChangeCallback; private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; + private @Nullable Consumer<Rect> mOnPipExclusionBoundsChangeCallback; public PipBoundsState(@NonNull Context context) { mContext = context; @@ -102,6 +104,9 @@ public final class PipBoundsState { /** Set the current PIP bounds. */ public void setBounds(@NonNull Rect bounds) { mBounds.set(bounds); + if (mOnPipExclusionBoundsChangeCallback != null) { + mOnPipExclusionBoundsChangeCallback.accept(bounds); + } } /** Get the current PIP bounds. */ @@ -386,6 +391,18 @@ public final class PipBoundsState { mOnShelfVisibilityChangeCallback = onShelfVisibilityChangeCallback; } + /** + * Set a callback to watch out for PiP bounds. This is mostly used by SystemUI's + * Back-gesture handler, to avoid conflicting with PiP when it's stashed. + */ + public void setPipExclusionBoundsChangeCallback( + @Nullable Consumer<Rect> onPipExclusionBoundsChangeCallback) { + mOnPipExclusionBoundsChangeCallback = onPipExclusionBoundsChangeCallback; + if (mOnPipExclusionBoundsChangeCallback != null) { + mOnPipExclusionBoundsChangeCallback.accept(getBounds()); + } + } + /** Source of truth for the current bounds of PIP that may be in motion. */ public static class MotionBoundsState { /** The bounds used when PIP is in motion (e.g. during a drag or animation) */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index 97aeda4b053f..582ff2180c83 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -94,11 +94,14 @@ public class PipSurfaceTransactionHelper { public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees) { mTmpSourceRectF.set(sourceBounds); + // We want the matrix to position the surface relative to the screen coordinates so offset + // the source to 0,0 + mTmpSourceRectF.offsetTo(0, 0); mTmpDestinationRectF.set(destinationBounds); mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); - mTmpTransform.postRotate(degrees); - tx.setMatrix(leash, mTmpTransform, mTmpFloat9) - .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top); + mTmpTransform.postRotate(degrees, + mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY()); + tx.setMatrix(leash, mTmpTransform, mTmpFloat9); return this; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 9ec7c0d173dd..99ec10049340 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -41,6 +41,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PictureInPictureParams; @@ -62,6 +63,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; @@ -130,7 +132,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final PipAnimationController mPipAnimationController; private final PipTransitionController mPipTransitionController; private final PipUiEventLogger mPipUiEventLoggerLogger; - private final int mEnterExitAnimationDuration; + private final int mEnterAnimationDuration; + private final int mExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<LegacySplitScreenController> mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; @@ -224,8 +227,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipBoundsAlgorithm = boundsHandler; mPipMenuController = pipMenuController; mPipTransitionController = pipTransitionController; - mEnterExitAnimationDuration = context.getResources() - .getInteger(R.integer.config_pipResizeAnimationDuration); + mEnterAnimationDuration = context.getResources() + .getInteger(R.integer.config_pipEnterAnimationDuration); + mExitAnimationDuration = context.getResources() + .getInteger(R.integer.config_pipExitAnimationDuration); mSurfaceTransactionHelper = surfaceTransactionHelper; mPipAnimationController = pipAnimationController; mPipUiEventLoggerLogger = pipUiEventLogger; @@ -377,12 +382,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } // removePipImmediately is expected when the following animation finishes. - mPipAnimationController + ValueAnimator animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), 1f, 0f) .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK) - .setPipAnimationCallback(mPipAnimationCallback) - .setDuration(mEnterExitAnimationDuration) - .start(); + .setPipAnimationCallback(mPipAnimationCallback); + animator.setDuration(mExitAnimationDuration); + animator.setInterpolator(Interpolators.ALPHA_OUT); + animator.start(); mState = State.EXITING_PIP; } @@ -423,12 +429,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (mInSwipePipToHomeTransition) { final Rect destinationBounds = mPipBoundsState.getBounds(); + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + mSurfaceTransactionHelper.resetScale(tx, mLeash, destinationBounds); + mSurfaceTransactionHelper.crop(tx, mLeash, destinationBounds); // animation is finished in the Launcher and here we directly apply the final touch. applyEnterPipSyncTransaction(destinationBounds, () -> { // ensure menu's settled in its final bounds first finishResizeForMenu(destinationBounds); sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); - }); + }, tx); mInSwipePipToHomeTransition = false; return; } @@ -460,11 +470,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( info.pictureInPictureParams, currentBounds); scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */, - sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, + sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, null /* updateBoundsCallback */); mState = State.ENTERING_PIP; } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { - enterPipWithAlphaAnimation(destinationBounds, mEnterExitAnimationDuration); + enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration); mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType); @@ -490,16 +500,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // mState is set right after the animation is kicked off to block any resize // requests such as offsetPip that may have been called prior to the transition. mState = State.ENTERING_PIP; - }); + }, null /* boundsChangeTransaction */); } - private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable) { + private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable, + @Nullable SurfaceControl.Transaction boundsChangeTransaction) { // PiP menu is attached late in the process here to avoid any artifacts on the leash // caused by addShellRoot when in gesture navigation mode. mPipMenuController.attach(mLeash); final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); wct.setBounds(mToken, destinationBounds); + if (boundsChangeTransaction != null) { + wct.setBoundsChangeTransaction(mToken, boundsChangeTransaction); + } wct.scheduleFinishEnterPip(mToken, destinationBounds); mSyncTransactionQueue.queue(wct); if (runnable != null) { @@ -544,7 +558,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, */ @Override public void onTaskVanished(ActivityManager.RunningTaskInfo info) { - if (!mState.isInPip()) { + if (mState == State.UNDEFINED) { return; } final WindowContainerToken token = info.token; @@ -557,6 +571,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mInSwipePipToHomeTransition = false; mPictureInPictureParams = null; mState = State.UNDEFINED; + // Re-set the PIP bounds to none. + mPipBoundsState.setBounds(new Rect()); mPipUiEventLoggerLogger.setTaskInfo(null); mPipMenuController.detach(); @@ -585,7 +601,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds( mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio()); Objects.requireNonNull(destinationBounds, "Missing destination bounds"); - scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration, + scheduleAnimateResizePip(destinationBounds, mEnterAnimationDuration, null /* updateBoundsCallback */); } @@ -935,6 +951,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, || direction == TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND; // Animate with a cross-fade if enabled and seamless resize is disables by the app. final boolean animateCrossFadeResize = mayAnimateFinishResize + && mPictureInPictureParams != null && !mPictureInPictureParams.isSeamlessResizeEnabled(); if (animateCrossFadeResize) { // Take a snapshot of the PIP task and hide it. We'll show it and fade it out after @@ -954,7 +971,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mMainExecutor.execute(() -> { // Start animation to fade out the snapshot. final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f); - animator.setDuration(mEnterExitAnimationDuration); + animator.setDuration(mEnterAnimationDuration); animator.addUpdateListener(animation -> { final float alpha = (float) animation.getAnimatedValue(); final SurfaceControl.Transaction transaction = @@ -999,10 +1016,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); wct.scheduleFinishEnterPip(mToken, destinationBounds); } else if (isOutPipDirection(direction)) { - // If we are animating to fullscreen, then we need to reset the override bounds - // on the task to ensure that the task "matches" the parent's bounds. - taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP) - ? null : destinationBounds; + // If we are animating to fullscreen or split screen, then we need to reset the + // override bounds on the task to ensure that the task "matches" the parent's bounds. + taskBounds = null; applyWindowingModeChangeOnExit(wct, direction); } else { // Just a resize in PIP diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 580861cf4974..65f3d3a92476 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -244,9 +244,11 @@ public class PhonePipMenuController implements PipMenuController { */ public void showMenuWithPossibleDelay(int menuState, Rect stackBounds, boolean allowMenuTimeout, boolean willResizeMenu, boolean showResizeHandle) { - // hide all visible controls including close button and etc. first, this is to ensure - // menu is totally invisible during the transition to eliminate unpleasant artifacts - fadeOutMenu(); + if (willResizeMenu) { + // hide all visible controls including close button and etc. first, this is to ensure + // menu is totally invisible during the transition to eliminate unpleasant artifacts + fadeOutMenu(); + } showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu, willResizeMenu /* withDelay=willResizeMenu here */, showResizeHandle); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 9a584c67f97c..d75c1d65614d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.view.WindowManager.INPUT_CONSUMER_PIP; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import android.app.ActivityManager; @@ -33,6 +34,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -44,6 +46,7 @@ import android.view.DisplayInfo; import android.view.WindowManagerGlobal; import android.window.WindowContainerTransaction; +import androidx.annotation.BinderThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -51,9 +54,14 @@ import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.onehanded.OneHandedTransitionCallback; +import com.android.wm.shell.pip.IPip; +import com.android.wm.shell.pip.IPipAnimationListener; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsAlgorithm; @@ -66,12 +74,14 @@ import com.android.wm.shell.pip.PipUtils; import java.io.PrintWriter; import java.util.Objects; +import java.util.Optional; import java.util.function.Consumer; /** * Manages the picture-in-picture (PIP) UI and states for Phones. */ -public class PipController implements PipTransitionController.PipTransitionCallback { +public class PipController implements PipTransitionController.PipTransitionCallback, + RemoteCallable<PipController> { private static final String TAG = "PipController"; private Context mContext; @@ -85,12 +95,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb private PipBoundsState mPipBoundsState; private PipTouchHandler mTouchHandler; private PipTransitionController mPipTransitionController; - protected final PipImpl mImpl = new PipImpl(); + private Optional<OneHandedController> mOneHandedController; + protected final PipImpl mImpl; private final Rect mTmpInsetBounds = new Rect(); private boolean mIsInFixedRotation; - private Consumer<Boolean> mPinnedStackAnimationRecentsCallback; + private IPipAnimationListener mPinnedStackAnimationRecentsCallback; protected PhonePipMenuController mMenuController; protected PipTaskOrganizer mPipTaskOrganizer; @@ -230,7 +241,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, - TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) { + TaskStackListenerImpl taskStackListener, + Optional<OneHandedController> oneHandedController, + ShellExecutor mainExecutor) { if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { Slog.w(TAG, "Device doesn't support Pip feature"); return null; @@ -239,7 +252,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, - taskStackListener, mainExecutor) + taskStackListener, oneHandedController, mainExecutor) .mImpl; } @@ -255,6 +268,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, TaskStackListenerImpl taskStackListener, + Optional<OneHandedController> oneHandedController, ShellExecutor mainExecutor ) { // Ensure that we are the primary user's SystemUI. @@ -264,6 +278,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } mContext = context; + mImpl = new PipImpl(); mWindowManagerShellWrapper = windowManagerShellWrapper; mDisplayController = displayController; mPipBoundsAlgorithm = pipBoundsAlgorithm; @@ -274,6 +289,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mMenuController = phonePipMenuController; mTouchHandler = pipTouchHandler; mAppOpsListener = pipAppOpsListener; + mOneHandedController = oneHandedController; mPipTransitionController = pipTransitionController; mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), INPUT_CONSUMER_PIP, mainExecutor); @@ -364,6 +380,31 @@ public class PipController implements PipTransitionController.PipTransitionCallb clearedTask /* skipAnimation */); } }); + + mOneHandedController.ifPresent(controller -> { + controller.asOneHanded().registerTransitionCallback( + new OneHandedTransitionCallback() { + @Override + public void onStartFinished(Rect bounds) { + mTouchHandler.setOhmOffset(bounds.top); + } + + @Override + public void onStopFinished(Rect bounds) { + mTouchHandler.setOhmOffset(bounds.top); + } + }); + }); + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; } private void onConfigurationChanged(Configuration newConfig) { @@ -474,7 +515,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipTaskOrganizer.setOneShotAnimationType(animationType); } - private void setPinnedStackAnimationListener(Consumer<Boolean> callback) { + private void setPinnedStackAnimationListener(IPipAnimationListener callback) { mPinnedStackAnimationRecentsCallback = callback; } @@ -494,15 +535,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds); } - /** - * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's - * Back-gesture handler, to avoid conflicting with PiP when it's stashed. - */ - private void setPipExclusionBoundsChangeListener( - Consumer<Rect> pipExclusionBoundsChangeListener) { - mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener); - } - @Override public void onPipTransitionStarted(int direction, Rect pipBounds) { if (isOutPipDirection(direction)) { @@ -512,7 +544,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Disable touches while the animation is running mTouchHandler.setTouchEnabled(false); if (mPinnedStackAnimationRecentsCallback != null) { - mPinnedStackAnimationRecentsCallback.accept(true); + try { + mPinnedStackAnimationRecentsCallback.onPipAnimationStarted(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call onPinnedStackAnimationStarted()", e); + } } } @@ -638,7 +674,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipInputConsumer.dump(pw, innerPrefix); } + /** + * The interface for calls from outside the Shell, within the host process. + */ private class PipImpl implements Pip { + private IPipImpl mIPip; + + @Override + public IPip createExternalInterface() { + if (mIPip != null) { + mIPip.invalidate(); + } + mIPip = new IPipImpl(PipController.this); + return mIPip; + } + @Override public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) { mMainExecutor.execute(() -> { @@ -696,13 +746,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void setPinnedStackAnimationListener(Consumer<Boolean> callback) { - mMainExecutor.execute(() -> { - PipController.this.setPinnedStackAnimationListener(callback); - }); - } - - @Override public void setPinnedStackAnimationType(int animationType) { mMainExecutor.execute(() -> { PipController.this.setPinnedStackAnimationType(animationType); @@ -712,7 +755,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Override public void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) { mMainExecutor.execute(() -> { - PipController.this.setPipExclusionBoundsChangeListener(listener); + mPipBoundsState.setPipExclusionBoundsChangeCallback(listener); }); } @@ -724,37 +767,99 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, - PictureInPictureParams pictureInPictureParams, int launcherRotation, - int shelfHeight) { - Rect[] result = new Rect[1]; + public void dump(PrintWriter pw) { try { mMainExecutor.executeBlocking(() -> { - result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo, - pictureInPictureParams, launcherRotation, shelfHeight); + PipController.this.dump(pw); }); } catch (InterruptedException e) { - Slog.e(TAG, "Failed to start swipe pip to home"); + Slog.e(TAG, "Failed to dump PipController in 2s"); } + } + } + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IPipImpl extends IPip.Stub { + private PipController mController; + private IPipAnimationListener mListener; + private final IBinder.DeathRecipient mListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + final PipController controller = mController; + controller.getRemoteCallExecutor().execute(() -> { + mListener = null; + controller.setPinnedStackAnimationListener(null); + }); + } + }; + + IPipImpl(PipController controller) { + mController = controller; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; + } + + @Override + public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, + PictureInPictureParams pictureInPictureParams, int launcherRotation, + int shelfHeight) { + Rect[] result = new Rect[1]; + executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome", + (controller) -> { + result[0] = controller.startSwipePipToHome(componentName, activityInfo, + pictureInPictureParams, launcherRotation, shelfHeight); + }, true /* blocking */); return result[0]; } @Override public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { - mMainExecutor.execute(() -> { - PipController.this.stopSwipePipToHome(componentName, destinationBounds); - }); + executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome", + (controller) -> { + controller.stopSwipePipToHome(componentName, destinationBounds); + }); } @Override - public void dump(PrintWriter pw) { - try { - mMainExecutor.executeBlocking(() -> { - PipController.this.dump(pw); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to dump PipController in 2s"); - } + public void setShelfHeight(boolean visible, int height) { + executeRemoteCallWithTaskPermission(mController, "setShelfHeight", + (controller) -> { + controller.setShelfHeight(visible, height); + }); + } + + @Override + public void setPinnedStackAnimationListener(IPipAnimationListener listener) { + executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener", + (controller) -> { + if (mListener != null) { + // Reset the old death recipient + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + if (listener != null) { + // Register the death recipient for the new listener to clear the listener + try { + listener.asBinder().linkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } + } + mListener = listener; + controller.setPinnedStackAnimationListener(listener); + }); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index 9ee6a221c80c..c26b686f91fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -16,13 +16,14 @@ package com.android.wm.shell.pip.phone; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.TransitionDrawable; -import android.os.Handler; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -41,8 +42,6 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.pip.PipUiEventLogger; -import java.util.concurrent.TimeUnit; - import kotlin.Unit; /** @@ -89,7 +88,9 @@ public class PipDismissTargetHandler { // Allow dragging the PIP to a location to close it private boolean mEnableDismissDragToEdge; + private int mTargetSize; private int mDismissAreaHeight; + private float mMagneticFieldRadiusPercent = 1f; private final Context mContext; private final PipMotionHelper mMotionHelper; @@ -183,18 +184,27 @@ public class PipDismissTargetHandler { } final Resources res = mContext.getResources(); - final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); + mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); final FrameLayout.LayoutParams newParams = - new FrameLayout.LayoutParams(targetSize, targetSize); + new FrameLayout.LayoutParams(mTargetSize, mTargetSize); newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; newParams.bottomMargin = mContext.getResources().getDimensionPixelSize( R.dimen.floating_dismiss_bottom_margin); mTargetView.setLayoutParams(newParams); // Set the magnetic field radius equal to the target size from the center of the target - mMagneticTarget.setMagneticFieldRadiusPx( - (int) (targetSize * MAGNETIC_FIELD_RADIUS_MULTIPLIER)); + setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent); + } + + /** + * Increase or decrease the field radius of the magnet object, e.g. with larger percent, + * PiP will magnetize to the field sooner. + */ + public void setMagneticFieldRadiusPercent(float percent) { + mMagneticFieldRadiusPercent = percent; + mMagneticTarget.setMagneticFieldRadiusPx((int) (mMagneticFieldRadiusPercent * mTargetSize + * MAGNETIC_FIELD_RADIUS_MULTIPLIER)); } /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */ @@ -234,6 +244,7 @@ public class PipDismissTargetHandler { lp.setTitle("pip-dismiss-overlay"); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; lp.setFitInsetsTypes(0 /* types */); return lp; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 8ab405bca7db..1bfae53853c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -76,7 +76,6 @@ public class PipMenuView extends FrameLayout { private static final int INITIAL_DISMISS_DELAY = 3500; private static final int POST_INTERACTION_DISMISS_DELAY = 2000; private static final long MENU_FADE_DURATION = 125; - private static final long MENU_SLOW_FADE_DURATION = 175; private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30; private static final float MENU_BACKGROUND_ALPHA = 0.3f; @@ -253,9 +252,7 @@ public class PipMenuView extends FrameLayout { mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); - mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE - ? MENU_FADE_DURATION - : MENU_SLOW_FADE_DURATION); + mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); if (allowMenuTimeout) { mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index 81a7ae1be482..402846f79ab7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -29,7 +29,6 @@ import android.os.Looper; import android.util.Log; import android.view.Choreographer; -import androidx.annotation.VisibleForTesting; import androidx.dynamicanimation.animation.AnimationHandler; import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler; import androidx.dynamicanimation.animation.SpringForce; @@ -489,8 +488,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** * Animates the PiP to offset it from the IME or shelf. */ - @VisibleForTesting - public void animateToOffset(Rect originalBounds, int offset) { + void animateToOffset(Rect originalBounds, int offset) { if (DEBUG) { Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset + " callers=\n" + Debug.getCallers(5, " ")); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java index 805123f81d81..f8125fd5764e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java @@ -16,111 +16,115 @@ package com.android.wm.shell.pip.phone; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; /** * Helper class to calculate the new size given two-fingers pinch to resize. */ public class PipPinchResizingAlgorithm { - private static final Rect TMP_RECT = new Rect(); + + private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; + private static final float OVERROTATE_DAMP_FACTOR = 0.4f; + private static final float ANGLE_THRESHOLD = 5f; + + private final PointF mTmpDownVector = new PointF(); + private final PointF mTmpLastVector = new PointF(); + private final PointF mTmpDownCentroid = new PointF(); + private final PointF mTmpLastCentroid = new PointF(); + /** - * Given inputs and requirements and current PiP bounds, return the new size. - * - * @param x0 x-coordinate of the primary input. - * @param y0 y-coordinate of the primary input. - * @param x1 x-coordinate of the secondary input. - * @param y1 y-coordinate of the secondary input. - * @param downx0 x-coordinate of the original down point of the primary input. - * @param downy0 y-coordinate of the original down ponit of the primary input. - * @param downx1 x-coordinate of the original down point of the secondary input. - * @param downy1 y-coordinate of the original down point of the secondary input. - * @param currentPipBounds current PiP bounds. - * @param minVisibleWidth minimum visible width. - * @param minVisibleHeight minimum visible height. - * @param maxSize max size. - * @return The new resized PiP bounds, sharing the same center. + * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in + * degrees that the PIP should be rotated. */ - public static Rect pinchResize(float x0, float y0, float x1, float y1, - float downx0, float downy0, float downx1, float downy1, Rect currentPipBounds, - int minVisibleWidth, int minVisibleHeight, Point maxSize) { - - int width = currentPipBounds.width(); - int height = currentPipBounds.height(); - int left = currentPipBounds.left; - int top = currentPipBounds.top; - int right = currentPipBounds.right; - int bottom = currentPipBounds.bottom; - final float aspect = (float) width / (float) height; - final int widthDelta = Math.round(Math.abs(x0 - x1) - Math.abs(downx0 - downx1)); - final int heightDelta = Math.round(Math.abs(y0 - y1) - Math.abs(downy0 - downy1)); - final int dx = (int) ((x0 - downx0 + x1 - downx1) / 2); - final int dy = (int) ((y0 - downy0 + y1 - downy1) / 2); - - width = Math.max(minVisibleWidth, Math.min(width + widthDelta, maxSize.x)); - height = Math.max(minVisibleHeight, Math.min(height + heightDelta, maxSize.y)); - - // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major - // drag axis. What ever is producing the bigger rectangle will be chosen. - int width1; - int width2; - int height1; - int height2; - if (aspect > 1.0f) { - // Assuming that the width is our target we calculate the height. - width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width)); - height1 = Math.round((float) width1 / aspect); - if (height1 < minVisibleHeight) { - // If the resulting height is too small we adjust to the minimal size. - height1 = minVisibleHeight; - width1 = Math.max(minVisibleWidth, - Math.min(maxSize.x, Math.round((float) height1 * aspect))); - } - // Assuming that the height is our target we calculate the width. - height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height)); - width2 = Math.round((float) height2 * aspect); - if (width2 < minVisibleWidth) { - // If the resulting width is too small we adjust to the minimal size. - width2 = minVisibleWidth; - height2 = Math.max(minVisibleHeight, - Math.min(maxSize.y, Math.round((float) width2 / aspect))); - } - } else { - // Assuming that the width is our target we calculate the height. - width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width)); - height1 = Math.round((float) width1 / aspect); - if (height1 < minVisibleHeight) { - // If the resulting height is too small we adjust to the minimal size. - height1 = minVisibleHeight; - width1 = Math.max(minVisibleWidth, - Math.min(maxSize.x, Math.round((float) height1 * aspect))); - } - // Assuming that the height is our target we calculate the width. - height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height)); - width2 = Math.round((float) height2 * aspect); - if (width2 < minVisibleWidth) { - // If the resulting width is too small we adjust to the minimal size. - width2 = minVisibleWidth; - height2 = Math.max(minVisibleHeight, - Math.min(maxSize.y, Math.round((float) width2 / aspect))); - } - } + public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint, + PointF lastPoint, PointF lastSecondPoint, Point minSize, Point maxSize, + Rect initialBounds, Rect resizeBoundsOut) { + float downDist = (float) Math.hypot(downSecondPoint.x - downPoint.x, + downSecondPoint.y - downPoint.y); + float dist = (float) Math.hypot(lastSecondPoint.x - lastPoint.x, + lastSecondPoint.y - lastPoint.y); + float minScale = getMinScale(initialBounds, minSize); + float maxScale = getMaxScale(initialBounds, maxSize); + float scale = Math.max(minScale, Math.min(maxScale, dist / downDist)); + + // Scale the bounds by the change in distance between the points + resizeBoundsOut.set(initialBounds); + scaleRectAboutCenter(resizeBoundsOut, scale); + + // Translate by the centroid movement + getCentroid(downPoint, downSecondPoint, mTmpDownCentroid); + getCentroid(lastPoint, lastSecondPoint, mTmpLastCentroid); + resizeBoundsOut.offset((int) (mTmpLastCentroid.x - mTmpDownCentroid.x), + (int) (mTmpLastCentroid.y - mTmpDownCentroid.y)); + + // Calculate the angle + mTmpDownVector.set(downSecondPoint.x - downPoint.x, + downSecondPoint.y - downPoint.y); + mTmpLastVector.set(lastSecondPoint.x - lastPoint.x, + lastSecondPoint.y - lastPoint.y); + float angle = (float) Math.atan2(cross(mTmpDownVector, mTmpLastVector), + dot(mTmpDownVector, mTmpLastVector)); + return constrainRotationAngle((float) Math.toDegrees(angle)); + } + + private float getMinScale(Rect bounds, Point minSize) { + return Math.max((float) minSize.x / bounds.width(), (float) minSize.y / bounds.height()); + } + + private float getMaxScale(Rect bounds, Point maxSize) { + return Math.min((float) maxSize.x / bounds.width(), (float) maxSize.y / bounds.height()); + } + + private float constrainRotationAngle(float angle) { + // Remove some degrees so that user doesn't immediately start rotating until a threshold + return Math.signum(angle) * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD)); + } + + /** + * Given the current rotation angle, dampen it so that as it approaches the maximum angle, + * dampen it. + */ + private float dampedRotate(float amount) { + if (Float.compare(amount, 0) == 0) return 0; - // Use the bigger of the two rectangles if the major change was positive, otherwise - // do the opposite. - final boolean grows = width > (right - left) || height > (bottom - top); - if (grows == (width1 * height1 > width2 * height2)) { - width = width1; - height = height1; - } else { - width = width2; - height = height2; + float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION; + f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f))); + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); } + return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION; + } + + /** + * Returns a value that corresponds to y = (f - 1)^3 + 1. + */ + private float overRotateInfluenceCurve(float f) { + f -= 1.0f; + return f * f * f + 1.0f; + } - TMP_RECT.set(currentPipBounds.centerX() - width / 2, - currentPipBounds.centerY() - height / 2, - currentPipBounds.centerX() + width / 2, - currentPipBounds.centerY() + height / 2); - TMP_RECT.offset(dx, dy); - return TMP_RECT; + private void getCentroid(PointF p1, PointF p2, PointF centroidOut) { + centroidOut.set((p2.x + p1.x) / 2, (p2.y + p1.y) / 2); + } + + private float dot(PointF p1, PointF p2) { + return p1.x * p2.x + p1.y * p2.y; + } + + private float cross(PointF p1, PointF p2) { + return p1.x * p2.y - p1.y * p2.x; + } + + private void scaleRectAboutCenter(Rect r, float scale) { + if (scale != 1.0f) { + int cx = r.centerX(); + int cy = r.centerY(); + r.offset(-cx, -cy); + r.scale(scale); + r.offset(cx, cy); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index c726c012c6a0..588571f7171e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -63,10 +63,7 @@ public class PipResizeGestureHandler { private static final String TAG = "PipResizeGestureHandler"; private static final int PINCH_RESIZE_SNAP_DURATION = 250; - private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f; - private static final float OVERROTATE_DAMP_FACTOR = 0.4f; - private static final float ANGLE_THRESHOLD = 5f; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; @@ -74,18 +71,22 @@ public class PipResizeGestureHandler { private final PipBoundsState mPipBoundsState; private final PipTaskOrganizer mPipTaskOrganizer; private final PhonePipMenuController mPhonePipMenuController; + private final PipDismissTargetHandler mPipDismissTargetHandler; private final PipUiEventLogger mPipUiEventLogger; + private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; private final int mDisplayId; private final ShellExecutor mMainExecutor; private final Region mTmpRegion = new Region(); private final PointF mDownPoint = new PointF(); - private final PointF mDownSecondaryPoint = new PointF(); + private final PointF mDownSecondPoint = new PointF(); + private final PointF mLastPoint = new PointF(); + private final PointF mLastSecondPoint = new PointF(); private final Point mMaxSize = new Point(); private final Point mMinSize = new Point(); private final Rect mLastResizeBounds = new Rect(); private final Rect mUserResizeBounds = new Rect(); - private final Rect mLastDownBounds = new Rect(); + private final Rect mDownBounds = new Rect(); private final Rect mDragCornerSize = new Rect(); private final Rect mTmpTopLeftCorner = new Rect(); private final Rect mTmpTopRightCorner = new Rect(); @@ -103,12 +104,8 @@ public class PipResizeGestureHandler { private boolean mIsEnabled; private boolean mEnablePinchResize; private boolean mIsSysUiStateValid; - // For drag-resize private boolean mThresholdCrossed; - // For pinch-resize - private boolean mThresholdCrossed0; - private boolean mThresholdCrossed1; - private boolean mUsingPinchToZoom = true; + private boolean mOngoingPinchToResize = false; private float mAngle = 0; int mFirstIndex = -1; int mSecondIndex = -1; @@ -117,12 +114,14 @@ public class PipResizeGestureHandler { private InputEventReceiver mInputEventReceiver; private int mCtrlType; + private int mOhmOffset; public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, PipMotionHelper motionHelper, - PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier, - Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger, - PhonePipMenuController menuActivityController, ShellExecutor mainExecutor) { + PipTaskOrganizer pipTaskOrganizer, PipDismissTargetHandler pipDismissTargetHandler, + Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable, + PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, + ShellExecutor mainExecutor) { mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = mainExecutor; @@ -130,10 +129,12 @@ public class PipResizeGestureHandler { mPipBoundsState = pipBoundsState; mMotionHelper = motionHelper; mPipTaskOrganizer = pipTaskOrganizer; + mPipDismissTargetHandler = pipDismissTargetHandler; mMovementBoundsSupplier = movementBoundsSupplier; mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; + mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); } public void init() { @@ -236,7 +237,7 @@ public class PipResizeGestureHandler { } if (ev instanceof MotionEvent) { - if (mUsingPinchToZoom) { + if (mOngoingPinchToResize) { onPinchResize((MotionEvent) ev); } else { onDragCornerResize((MotionEvent) ev); @@ -248,7 +249,7 @@ public class PipResizeGestureHandler { * Checks if there is currently an on-going gesture, either drag-resize or pinch-resize. */ public boolean hasOngoingGesture() { - return mCtrlType != CTRL_NONE || mUsingPinchToZoom; + return mCtrlType != CTRL_NONE || mOngoingPinchToResize; } /** @@ -293,6 +294,10 @@ public class PipResizeGestureHandler { return mEnablePinchResize; } + public boolean isResizing() { + return mAllowGesture; + } + public boolean willStartResizeGesture(MotionEvent ev) { if (isInValidSysUiState()) { switch (ev.getActionMasked()) { @@ -305,7 +310,7 @@ public class PipResizeGestureHandler { case MotionEvent.ACTION_POINTER_DOWN: if (mEnablePinchResize && ev.getPointerCount() == 2) { onPinchResize(ev); - mUsingPinchToZoom = true; + mOngoingPinchToResize = true; return true; } break; @@ -361,6 +366,7 @@ public class PipResizeGestureHandler { if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { mFirstIndex = -1; mSecondIndex = -1; + mAllowGesture = false; finishResize(); } @@ -370,14 +376,16 @@ public class PipResizeGestureHandler { if (action == MotionEvent.ACTION_POINTER_DOWN) { if (mFirstIndex == -1 && mSecondIndex == -1) { + mAllowGesture = true; mFirstIndex = 0; mSecondIndex = 1; mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex)); - mDownSecondaryPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); - + mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); + mDownBounds.set(mPipBoundsState.getBounds()); - mLastDownBounds.set(mPipBoundsState.getBounds()); - mLastResizeBounds.set(mLastDownBounds); + mLastPoint.set(mDownPoint); + mLastSecondPoint.set(mLastSecondPoint); + mLastResizeBounds.set(mDownBounds); } } @@ -390,137 +398,40 @@ public class PipResizeGestureHandler { float y0 = ev.getRawY(mFirstIndex); float x1 = ev.getRawX(mSecondIndex); float y1 = ev.getRawY(mSecondIndex); + mLastPoint.set(x0, y0); + mLastSecondPoint.set(x1, y1); - double hypot0 = Math.hypot(x0 - mDownPoint.x, y0 - mDownPoint.y); - double hypot1 = Math.hypot(x1 - mDownSecondaryPoint.x, y1 - mDownSecondaryPoint.y); // Capture inputs - if (hypot0 > mTouchSlop && !mThresholdCrossed0) { - mInputMonitor.pilferPointers(); - mThresholdCrossed0 = true; - // Reset the down to begin resizing from this point - mDownPoint.set(x0, y0); - } - if (hypot1 > mTouchSlop && !mThresholdCrossed1) { + if (!mThresholdCrossed + && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop + || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) { mInputMonitor.pilferPointers(); - mThresholdCrossed1 = true; + mThresholdCrossed = true; // Reset the down to begin resizing from this point - mDownSecondaryPoint.set(x1, y1); + mDownPoint.set(mLastPoint); + mDownSecondPoint.set(mLastSecondPoint); } - if (mThresholdCrossed0 || mThresholdCrossed1) { + + if (mThresholdCrossed) { if (mPhonePipMenuController.isMenuVisible()) { mPhonePipMenuController.hideMenu(); } - x0 = mThresholdCrossed0 ? x0 : mDownPoint.x; - y0 = mThresholdCrossed0 ? y0 : mDownPoint.y; - x1 = mThresholdCrossed1 ? x1 : mDownSecondaryPoint.x; - y1 = mThresholdCrossed1 ? y1 : mDownSecondaryPoint.y; - - final Rect originalPipBounds = mPipBoundsState.getBounds(); - int focusX = (int) originalPipBounds.centerX(); - int focusY = (int) originalPipBounds.centerY(); - - float down0X = mDownPoint.x; - float down0Y = mDownPoint.y; - float down1X = mDownSecondaryPoint.x; - float down1Y = mDownSecondaryPoint.y; - - float angle = 0; - if (down0X > focusX && down0Y < focusY && down1X < focusX && down1Y > focusY) { - // Top right + Bottom left pinch to zoom. - angle = calculateRotationAngle(mLastResizeBounds.centerX(), - mLastResizeBounds.centerY(), x0, y0, x1, y1, true); - } else if (down1X > focusX && down1Y < focusY - && down0X < focusX && down0Y > focusY) { - // Top right + Bottom left pinch to zoom. - angle = calculateRotationAngle(mLastResizeBounds.centerX(), - mLastResizeBounds.centerY(), x1, y1, x0, y0, true); - } else if (down0X < focusX && down0Y < focusY - && down1X > focusX && down1Y > focusY) { - // Top left + bottom right pinch to zoom. - angle = calculateRotationAngle(mLastResizeBounds.centerX(), - mLastResizeBounds.centerY(), x0, y0, x1, y1, false); - } else if (down1X < focusX && down1Y < focusY - && down0X > focusX && down0Y > focusY) { - // Top left + bottom right pinch to zoom. - angle = calculateRotationAngle(mLastResizeBounds.centerX(), - mLastResizeBounds.centerY(), x1, y1, x0, y0, false); - } - mAngle = angle; + mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint, + mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize, + mDownBounds, mLastResizeBounds); - mLastResizeBounds.set(PipPinchResizingAlgorithm.pinchResize(x0, y0, x1, y1, - mDownPoint.x, mDownPoint.y, mDownSecondaryPoint.x, mDownSecondaryPoint.y, - originalPipBounds, mMinSize.x, mMinSize.y, mMaxSize)); - - mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, - (float) -mAngle, null); + mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, + mAngle, null); mPipBoundsState.setHasUserResizedPip(true); } } } - private float calculateRotationAngle(int pivotX, int pivotY, float topX, float topY, - float bottomX, float bottomY, boolean positive) { - - // The base angle is the angle formed by taking the angle between the center horizontal - // and one of the corners. - double baseAngle = Math.toDegrees(Math.atan2(Math.abs(mLastResizeBounds.top - pivotY), - Math.abs(mLastResizeBounds.right - pivotX))); - - double angle0 = mThresholdCrossed0 - ? Math.toDegrees(Math.atan2(pivotY - topY, topX - pivotX)) : baseAngle; - double angle1 = mThresholdCrossed0 - ? Math.toDegrees(Math.atan2(pivotY - bottomY, bottomX - pivotX)) : baseAngle; - - - if (positive) { - angle1 = angle1 < 0 ? 180 + angle1 : angle1 - 180; - } else { - angle0 = angle0 < 0 ? -angle0 - 180 : 180 - angle0; - angle1 = -angle1; - } - - // Calculate the percentage difference of [0, 90] compare to the base angle. - double diff0 = (Math.max(-90, Math.min(angle0, 90)) - baseAngle) / 90; - double diff1 = (Math.max(-90, Math.min(angle1, 90)) - baseAngle) / 90; - - final float angle = - (float) (diff0 + diff1) / 2 * PINCH_RESIZE_MAX_ANGLE_ROTATION * (positive ? 1 : -1); - - // Remove some degrees so that user doesn't immediately start rotating until a threshold - return angle / Math.abs(angle) - * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD)); - } - - /** - * Given the current rotation angle, dampen it so that as it approaches the maximum angle, - * dampen it. - */ - private float dampedRotate(float amount) { - if (Float.compare(amount, 0) == 0) return 0; - - float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION; - f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f))); - - // Clamp this factor, f, to -1 < f < 1 - if (Math.abs(f) >= 1) { - f /= Math.abs(f); - } - return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION; - } - - /** - * Returns a value that corresponds to y = (f - 1)^3 + 1. - */ - private float overRotateInfluenceCurve(float f) { - f -= 1.0f; - return f * f * f + 1.0f; - } - private void onDragCornerResize(MotionEvent ev) { int action = ev.getActionMasked(); float x = ev.getX(); - float y = ev.getY(); + float y = ev.getY() - mOhmOffset; if (action == MotionEvent.ACTION_DOWN) { final Rect currentPipBounds = mPipBoundsState.getBounds(); mLastResizeBounds.setEmpty(); @@ -528,9 +439,9 @@ public class PipResizeGestureHandler { if (mAllowGesture) { setCtrlType((int) x, (int) y); mDownPoint.set(x, y); - mLastDownBounds.set(mPipBoundsState.getBounds()); + mDownBounds.set(mPipBoundsState.getBounds()); } - if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY()) + if (!currentPipBounds.contains((int) x, (int) y) && mPhonePipMenuController.isMenuVisible()) { mPhonePipMenuController.hideMenu(); } @@ -559,11 +470,11 @@ public class PipResizeGestureHandler { mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y, mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize, true, - mLastDownBounds.width() > mLastDownBounds.height())); + mDownBounds.width() > mDownBounds.height())); mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds, mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */, true /* useCurrentSize */); - mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, + mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, null); mPipBoundsState.setHasUserResizedPip(true); } @@ -587,21 +498,23 @@ public class PipResizeGestureHandler { // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped // position correctly. Drag-resize does not need to move, so just finalize resize. - if (mUsingPinchToZoom) { + if (mOngoingPinchToResize) { final Rect startBounds = new Rect(mLastResizeBounds); // If user resize is pretty close to max size, just auto resize to max. if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { - mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y); + resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); } final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds); mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, - PINCH_RESIZE_SNAP_DURATION, -mAngle, callback); + PINCH_RESIZE_SNAP_DURATION, mAngle, callback); } else { mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback); } + mPipDismissTargetHandler + .setMagneticFieldRadiusPercent((float) mLastResizeBounds.width() / mMinSize.x); mPipUiEventLogger.log( PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); } else { @@ -612,7 +525,7 @@ public class PipResizeGestureHandler { private void resetState() { mCtrlType = CTRL_NONE; mAngle = 0; - mUsingPinchToZoom = false; + mOngoingPinchToResize = false; mAllowGesture = false; mThresholdCrossed = false; } @@ -637,6 +550,24 @@ public class PipResizeGestureHandler { mMinSize.set(minX, minY); } + void setOhmOffset(int offset) { + mOhmOffset = offset; + } + + private float distanceBetween(PointF p1, PointF p2) { + return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y); + } + + private void resizeRectAboutCenter(Rect rect, int w, int h) { + int cx = rect.centerX(); + int cy = rect.centerY(); + int l = cx - w / 2; + int r = l + w; + int t = cy - h / 2; + int b = t + h; + rect.set(l, t, r, b); + } + public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); @@ -645,6 +576,7 @@ public class PipResizeGestureHandler { pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled); pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize); pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed); + pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset); } class PipResizeInputEventReceiver extends BatchedInputEventReceiver { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 543ecfcf1a33..d474b6638e4a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -57,8 +57,6 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipUiEventLogger; import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.util.function.Consumer; /** * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding @@ -80,7 +78,6 @@ public class PipTouchHandler { private final ShellExecutor mMainExecutor; private PipResizeGestureHandler mPipResizeGestureHandler; - private WeakReference<Consumer<Rect>> mPipExclusionBoundsChangeListener; private final PhonePipMenuController mMenuController; private final AccessibilityManager mAccessibilityManager; @@ -179,13 +176,13 @@ public class PipTouchHandler { mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer, mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(), pipTransitionController, floatingContentCoordinator); - mPipResizeGestureHandler = - new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, - mMotionHelper, pipTaskOrganizer, this::getMovementBounds, - this::updateMovementBounds, pipUiEventLogger, menuController, - mainExecutor); mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger, mMotionHelper, mainExecutor); + mPipResizeGestureHandler = + new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, + mMotionHelper, pipTaskOrganizer, mPipDismissTargetHandler, + this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger, + menuController, mainExecutor); mTouchState = new PipTouchState(ViewConfiguration.get(context), () -> { if (mPipBoundsState.isStashed()) { @@ -288,11 +285,6 @@ public class PipTouchHandler { mFloatingContentCoordinator.onContentRemoved(mMotionHelper); } - // Reset exclusion to none. - if (mPipExclusionBoundsChangeListener != null - && mPipExclusionBoundsChangeListener.get() != null) { - mPipExclusionBoundsChangeListener.get().accept(new Rect()); - } mPipResizeGestureHandler.onActivityUnpinned(); } @@ -672,7 +664,7 @@ public class PipTouchHandler { } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) { // Try and restore the PiP to the closest edge, using the saved snap fraction // if possible - if (resize) { + if (resize && !mPipResizeGestureHandler.isResizing()) { if (mDeferResizeToNormalBoundsUntilRotation == -1) { // This is a very special case: when the menu is expanded and visible, // navigating to another activity can trigger auto-enter PiP, and if the @@ -790,7 +782,6 @@ public class PipTouchHandler { private final Point mStartPosition = new Point(); private final PointF mDelta = new PointF(); private boolean mShouldHideMenuAfterFling; - private float mDownSavedFraction = -1f; @Override public void onDown(PipTouchState touchState) { @@ -804,7 +795,6 @@ public class PipTouchHandler { mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mPipBoundsState.getMovementBounds().bottom; mMotionHelper.setSpringingToTouch(false); - mDownSavedFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it @@ -875,9 +865,12 @@ public class PipTouchHandler { if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) { mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */); } else { - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); - mPipBoundsState.setStashed(STASH_TYPE_NONE); + if (mPipBoundsState.isStashed()) { + // Reset stashed state if previously stashed + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); + mPipBoundsState.setStashed(STASH_TYPE_NONE); + } mMotionHelper.flingToSnapTarget(vel.x, vel.y, this::flingEndAction /* endAction */); } @@ -904,19 +897,19 @@ public class PipTouchHandler { mMotionHelper.expandLeavePip(); } } else if (mMenuState != MENU_STATE_FULL) { - if (!mTouchState.isWaitingForDoubleTap()) { - if (mPipBoundsState.isStashed()) { - animateToUnStashedState(); - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); - mPipBoundsState.setStashed(STASH_TYPE_NONE); - } else { - // User has stalled long enough for this not to be a drag or a double tap, - // just expand the menu - mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), - true /* allowMenuTimeout */, willResizeMenu(), - shouldShowResizeHandle()); - } + if (mPipBoundsState.isStashed()) { + // Unstash immediately if stashed, and don't wait for the double tap timeout + animateToUnStashedState(); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); + mPipBoundsState.setStashed(STASH_TYPE_NONE); + mTouchState.removeDoubleTapTimeoutCallback(); + } else if (!mTouchState.isWaitingForDoubleTap()) { + // User has stalled long enough for this not to be a drag or a double tap, + // just expand the menu + mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), + true /* allowMenuTimeout */, willResizeMenu(), + shouldShowResizeHandle()); } else { // Next touch event _may_ be the second tap for the double-tap, schedule a // fallback runnable to trigger the menu if no touch event occurs before the @@ -924,15 +917,10 @@ public class PipTouchHandler { mTouchState.scheduleDoubleTapTimeoutCallback(); } } - mDownSavedFraction = -1f; return true; } private void stashEndAction() { - if (mPipExclusionBoundsChangeListener != null - && mPipExclusionBoundsChangeListener.get() != null) { - mPipExclusionBoundsChangeListener.get().accept(mPipBoundsState.getBounds()); - } if (mPipBoundsState.getBounds().left < 0 && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) { mPipUiEventLogger.log( @@ -944,6 +932,7 @@ public class PipTouchHandler { PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT); mPipBoundsState.setStashed(STASH_TYPE_RIGHT); } + mMenuController.hideMenu(); } private void flingEndAction() { @@ -952,11 +941,6 @@ public class PipTouchHandler { // dismiss overlay, so just finish it after the animation completes mMenuController.hideMenu(); } - // Reset exclusion to none. - if (mPipExclusionBoundsChangeListener != null - && mPipExclusionBoundsChangeListener.get() != null) { - mPipExclusionBoundsChangeListener.get().accept(new Rect()); - } } private boolean shouldStash(PointF vel, Rect motionBounds) { @@ -980,11 +964,6 @@ public class PipTouchHandler { } } - void setPipExclusionBoundsChangeListener(Consumer<Rect> pipExclusionBoundsChangeListener) { - mPipExclusionBoundsChangeListener = new WeakReference<>(pipExclusionBoundsChangeListener); - pipExclusionBoundsChangeListener.accept(mPipBoundsState.getBounds()); - } - /** * Updates the current movement bounds based on whether the menu is currently visible and * resized. @@ -1035,6 +1014,10 @@ public class PipTouchHandler { : mPipBoundsState.getBounds(); } + void setOhmOffset(int offset) { + mPipResizeGestureHandler.setOhmOffset(offset); + } + public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 2b0a0cd3de20..963a3dc70262 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -28,7 +28,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { // with those in the framework ProtoLogGroup WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), - WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl new file mode 100644 index 000000000000..0c46eaba18ae --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.splitscreen; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; + +import com.android.wm.shell.splitscreen.ISplitScreenListener; + +/** + * Interface that is exposed to remote callers to manipulate the splitscreen feature. + */ +interface ISplitScreen { + + /** + * Registers a split screen listener. + */ + oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1; + + /** + * Unregisters a split screen listener. + */ + oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2; + + /** + * Hides the side-stage if it is currently visible. + */ + oneway void setSideStageVisibility(boolean visible) = 3; + + /** + * Removes a task from the side stage. + */ + oneway void removeFromSideStage(int taskId) = 4; + + /** + * Removes the split-screen stages. + */ + oneway void exitSplitScreen() = 5; + + /** + * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. + */ + oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6; + + /** + * Starts a task in a stage. + */ + oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7; + + /** + * Starts a shortcut in a stage. + */ + oneway void startShortcut(String packageName, String shortcutId, int stage, int position, + in Bundle options, in UserHandle user) = 8; + + /** + * Starts an activity in a stage. + */ + oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage, + int position, in Bundle options) = 9; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl new file mode 100644 index 000000000000..faab4c2009cf --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.splitscreen; + +/** + * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. + */ +oneway interface ISplitScreenListener { + + /** + * Called when the stage position changes. + */ + void onStagePositionChanged(int stage, int position); + + /** + * Called when a task changes stages. + */ + void onTaskStageChanged(int taskId, int stage, boolean visible); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index e7cd38fb4bca..01a81d2ff5e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -40,15 +40,17 @@ class SideStage extends StageTaskListener { void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds, WindowContainerTransaction wct) { final WindowContainerToken rootToken = mRootTaskInfo.token; - wct.setHidden(rootToken, false) - .setBounds(rootToken, rootBounds) + wct.setBounds(rootToken, rootBounds) .reparent(task.token, rootToken, true /* onTop*/) // Moving the root task to top after the child tasks were repareted , or the root // task cannot be visible and focused. - .reorder(rootToken, true); + .reorder(rootToken, true /* onTop */); } boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { + // No matter if the root task is empty or not, moving the root to bottom because it no + // longer preserves visible child task. + wct.reorder(mRootTaskInfo.token, false /* onTop */); if (mChildrenTaskInfo.size() == 0) return false; wct.reparentTasks( mRootTaskInfo.token, @@ -62,10 +64,7 @@ class SideStage extends StageTaskListener { boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId); if (task == null) return false; - - wct.setHidden(mRootTaskInfo.token, true) - .reorder(mRootTaskInfo.token, false) - .reparent(task.token, newParent, false /* onTop */); + wct.reparent(task.token, newParent, false /* onTop */); return true; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 25a84bd46484..340b55d7f446 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -35,7 +35,7 @@ import com.android.wm.shell.draganddrop.DragAndDropPolicy; * TODO: Figure out which of these are actually needed outside of the Shell */ @ExternalThread -public interface SplitScreen extends DragAndDropPolicy.Starter { +public interface SplitScreen { /** * Stage position isn't specified normally meaning to use what ever it is currently set to. */ @@ -89,35 +89,10 @@ public interface SplitScreen extends DragAndDropPolicy.Starter { void onTaskStageChanged(int taskId, @StageType int stage, boolean visible); } - /** @return {@code true} if split-screen is currently visible. */ - boolean isSplitScreenVisible(); - /** Moves a task in the side-stage of split-screen. */ - boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition); - /** Moves a task in the side-stage of split-screen. */ - boolean moveToSideStage(ActivityManager.RunningTaskInfo task, - @StagePosition int sideStagePosition); - /** Removes a task from the side-stage of split-screen. */ - boolean removeFromSideStage(int taskId); - /** Sets the position of the side-stage. */ - void setSideStagePosition(@StagePosition int sideStagePosition); - /** Hides the side-stage if it is currently visible. */ - void setSideStageVisibility(boolean visible); - - /** Removes the split-screen stages. */ - void exitSplitScreen(); - /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */ - void exitSplitScreenOnHide(boolean exitSplitScreenOnHide); - /** Gets the stage bounds. */ - void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds); - - void registerSplitScreenListener(SplitScreenListener listener); - void unregisterSplitScreenListener(SplitScreenListener listener); - - void startTask(int taskId, - @StageType int stage, @StagePosition int position, @Nullable Bundle options); - void startShortcut(String packageName, String shortcutId, @StageType int stage, - @StagePosition int position, @Nullable Bundle options, UserHandle user); - void startIntent(PendingIntent intent, Context context, - @Nullable Intent fillInIntent, @StageType int stage, - @StagePosition int position, @Nullable Bundle options); + /** + * Returns a binder that can be passed to an external process to manipulate SplitScreen. + */ + default ISplitScreen createExternalInterface() { + return null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index bb6f6f259a1e..d4362efe462d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED; @@ -34,19 +35,24 @@ import android.content.Intent; import android.content.pm.LauncherApps; import android.graphics.Rect; import android.os.Bundle; +import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; +import androidx.annotation.BinderThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropPolicy; +import com.android.wm.shell.splitscreen.ISplitScreenListener; import java.io.PrintWriter; @@ -55,7 +61,8 @@ import java.io.PrintWriter; * {@link SplitScreen}. * @see StageCoordinator */ -public class SplitScreenController implements DragAndDropPolicy.Starter { +public class SplitScreenController implements DragAndDropPolicy.Starter, + RemoteCallable<SplitScreenController> { private static final String TAG = SplitScreenController.class.getSimpleName(); private final ShellTaskOrganizer mTaskOrganizer; @@ -84,6 +91,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { return mImpl; } + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + public void onOrganizerRegistered() { if (mStageCoordinator == null) { // TODO: Multi-display @@ -172,13 +189,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { } } - public void startIntent(PendingIntent intent, Context context, - Intent fillInIntent, @SplitScreen.StageType int stage, - @SplitScreen.StagePosition int position, @Nullable Bundle options) { + public void startIntent(PendingIntent intent, Intent fillInIntent, + @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position, + @Nullable Bundle options) { options = resolveStartStage(stage, position, options); try { - intent.send(context, 0, fillInIntent, null, null, null, options); + intent.send(mContext, 0, fillInIntent, null, null, null, options); } catch (PendingIntent.CanceledException e) { Slog.e(TAG, "Failed to launch activity", e); } @@ -242,121 +259,170 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { } } + /** + * The interface for calls from outside the Shell, within the host process. + */ + @ExternalThread private class SplitScreenImpl implements SplitScreen { - @Override - public boolean isSplitScreenVisible() { - return mMainExecutor.executeBlockingForResult(() -> { - return SplitScreenController.this.isSplitScreenVisible(); - }, Boolean.class); - } + private ISplitScreenImpl mISplitScreen; @Override - public boolean moveToSideStage(int taskId, int sideStagePosition) { - return mMainExecutor.executeBlockingForResult(() -> { - return SplitScreenController.this.moveToSideStage(taskId, sideStagePosition); - }, Boolean.class); + public ISplitScreen createExternalInterface() { + if (mISplitScreen != null) { + mISplitScreen.invalidate(); + } + mISplitScreen = new ISplitScreenImpl(SplitScreenController.this); + return mISplitScreen; } + } - @Override - public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, - int sideStagePosition) { - return mMainExecutor.executeBlockingForResult(() -> { - return SplitScreenController.this.moveToSideStage(task, sideStagePosition); - }, Boolean.class); - } + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class ISplitScreenImpl extends ISplitScreen.Stub { + private SplitScreenController mController; + private ISplitScreenListener mListener; + private final SplitScreen.SplitScreenListener mSplitScreenListener = + new SplitScreen.SplitScreenListener() { + @Override + public void onStagePositionChanged(int stage, int position) { + try { + if (mListener != null) { + mListener.onStagePositionChanged(stage, position); + } + } catch (RemoteException e) { + Slog.e(TAG, "onStagePositionChanged", e); + } + } - @Override - public boolean removeFromSideStage(int taskId) { - return mMainExecutor.executeBlockingForResult(() -> { - return SplitScreenController.this.removeFromSideStage(taskId); - }, Boolean.class); + @Override + public void onTaskStageChanged(int taskId, int stage, boolean visible) { + try { + if (mListener != null) { + mListener.onTaskStageChanged(taskId, stage, visible); + } + } catch (RemoteException e) { + Slog.e(TAG, "onTaskStageChanged", e); + } + } + }; + private final IBinder.DeathRecipient mListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + final SplitScreenController controller = mController; + controller.getRemoteCallExecutor().execute(() -> { + mListener = null; + controller.unregisterSplitScreenListener(mSplitScreenListener); + }); + } + }; + + public ISplitScreenImpl(SplitScreenController controller) { + mController = controller; } - @Override - public void setSideStagePosition(int sideStagePosition) { - mMainExecutor.execute(() -> { - SplitScreenController.this.setSideStagePosition(sideStagePosition); - }); + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; } @Override - public void setSideStageVisibility(boolean visible) { - mMainExecutor.execute(() -> { - SplitScreenController.this.setSideStageVisibility(visible); - }); + public void registerSplitScreenListener(ISplitScreenListener listener) { + executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener", + (controller) -> { + if (mListener != null) { + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + if (listener != null) { + try { + listener.asBinder().linkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } + } + mListener = listener; + controller.registerSplitScreenListener(mSplitScreenListener); + }); } @Override - public void enterSplitScreen(int taskId, boolean leftOrTop) { - mMainExecutor.execute(() -> { - SplitScreenController.this.enterSplitScreen(taskId, leftOrTop); - }); + public void unregisterSplitScreenListener(ISplitScreenListener listener) { + executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener", + (controller) -> { + if (mListener != null) { + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + mListener = null; + controller.unregisterSplitScreenListener(mSplitScreenListener); + }); } @Override public void exitSplitScreen() { - mMainExecutor.execute(() -> { - SplitScreenController.this.exitSplitScreen(); - }); + executeRemoteCallWithTaskPermission(mController, "exitSplitScreen", + (controller) -> { + controller.exitSplitScreen(); + }); } @Override public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { - mMainExecutor.execute(() -> { - SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide); - }); + executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide", + (controller) -> { + controller.exitSplitScreenOnHide(exitSplitScreenOnHide); + }); } @Override - public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { - try { - mMainExecutor.executeBlocking(() -> { - SplitScreenController.this.getStageBounds(outTopOrLeftBounds, - outBottomOrRightBounds); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to get stage bounds in 2s"); - } - } - - @Override - public void registerSplitScreenListener(SplitScreenListener listener) { - mMainExecutor.execute(() -> { - SplitScreenController.this.registerSplitScreenListener(listener); - }); + public void setSideStageVisibility(boolean visible) { + executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility", + (controller) -> { + controller.setSideStageVisibility(visible); + }); } @Override - public void unregisterSplitScreenListener(SplitScreenListener listener) { - mMainExecutor.execute(() -> { - SplitScreenController.this.unregisterSplitScreenListener(listener); - }); + public void removeFromSideStage(int taskId) { + executeRemoteCallWithTaskPermission(mController, "removeFromSideStage", + (controller) -> { + controller.removeFromSideStage(taskId); + }); } @Override public void startTask(int taskId, int stage, int position, @Nullable Bundle options) { - mMainExecutor.execute(() -> { - SplitScreenController.this.startTask(taskId, stage, position, options); - }); + executeRemoteCallWithTaskPermission(mController, "startTask", + (controller) -> { + controller.startTask(taskId, stage, position, options); + }); } @Override public void startShortcut(String packageName, String shortcutId, int stage, int position, @Nullable Bundle options, UserHandle user) { - mMainExecutor.execute(() -> { - SplitScreenController.this.startShortcut(packageName, shortcutId, stage, position, - options, user); - }); + executeRemoteCallWithTaskPermission(mController, "startShortcut", + (controller) -> { + controller.startShortcut(packageName, shortcutId, stage, position, + options, user); + }); } @Override - public void startIntent(PendingIntent intent, Context context, Intent fillInIntent, - int stage, int position, @Nullable Bundle options) { - mMainExecutor.execute(() -> { - SplitScreenController.this.startIntent(intent, context, fillInIntent, stage, - position, options); - }); + public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position, + @Nullable Bundle options) { + executeRemoteCallWithTaskPermission(mController, "startIntent", + (controller) -> { + controller.startIntent(intent, fillInIntent, stage, position, options); + }); } } - } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl new file mode 100644 index 000000000000..546c406ef453 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.startingsurface; + +import com.android.wm.shell.startingsurface.IStartingWindowListener; + +/** + * Interface that is exposed to remote callers to manipulate starting windows. + */ +interface IStartingWindow { + /** + * Sets listener to get task launching callbacks. + */ + oneway void setStartingWindowListener(IStartingWindowListener listener) = 43; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl new file mode 100644 index 000000000000..f562c8fc4f85 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.startingsurface; + +/** + * Listener interface that Launcher attaches to SystemUI to get + * callbacks when need a new starting window. + */ +interface IStartingWindowListener { + /** + * Notifies when Shell going to create a new starting window. + * @param taskId The task Id + * @param supportedType The starting window type + */ + oneway void onTaskLaunching(int taskId, int supportedType); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 3f9c2717731a..855faaa8e83e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -16,9 +16,14 @@ package com.android.wm.shell.startingsurface; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.os.UserHandle.getUserHandleForUid; + +import android.annotation.ColorInt; import android.annotation.NonNull; import android.app.ActivityThread; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -30,6 +35,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Build; +import android.os.Trace; import android.util.Slog; import android.view.SurfaceControl; import android.window.SplashScreenView; @@ -38,6 +44,7 @@ import com.android.internal.R; import com.android.internal.graphics.palette.Palette; import com.android.internal.graphics.palette.Quantizer; import com.android.internal.graphics.palette.VariationalKMeansQuantizer; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.TransactionPool; import java.util.List; @@ -58,6 +65,7 @@ public class SplashscreenContentDrawer { // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon. private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f); private final Context mContext; + private final IconProvider mIconProvider; private final int mMaxAnimatableIconDuration; private int mIconSize; @@ -69,10 +77,12 @@ public class SplashscreenContentDrawer { private int mIconNormalExitDistance; private int mIconEarlyExitDistance; private final TransactionPool mTransactionPool; + private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs(); SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration, int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) { mContext = context; + mIconProvider = new IconProvider(context); mMaxAnimatableIconDuration = maxAnimatableIconDuration; mAppRevealDuration = appRevealAnimDuration; mIconExitDuration = iconExitAnimDuration; @@ -108,87 +118,102 @@ public class SplashscreenContentDrawer { return new ColorDrawable(getSystemBGColor()); } - SplashScreenView makeSplashScreenContentView(Context context, int iconRes, - int splashscreenContentResId) { - updateDensity(); - // splash screen content will be deprecated after S. - final SplashScreenView ssc = - makeSplashscreenContentDrawable(context, splashscreenContentResId); - - if (ssc != null) { - return ssc; - } - - final SplashScreenWindowAttrs attrs = - SplashScreenWindowAttrs.createWindowAttrs(context); - final StartingWindowViewBuilder builder = new StartingWindowViewBuilder(); + private @ColorInt int peekWindowBGColor(Context context) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor"); final Drawable themeBGDrawable; - - if (attrs.mWindowBgColor != 0) { - themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor); - } else if (attrs.mWindowBgResId != 0) { - themeBGDrawable = context.getDrawable(attrs.mWindowBgResId); + if (mTmpAttrs.mWindowBgColor != 0) { + themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor); + } else if (mTmpAttrs.mWindowBgResId != 0) { + themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId); } else { - Slog.w(TAG, "Window background not exist!"); themeBGDrawable = createDefaultBackgroundDrawable(); + Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable); } + final int estimatedWindowBGColor = estimateWindowBGColor(themeBGDrawable); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + return estimatedWindowBGColor; + } + + private int estimateWindowBGColor(Drawable themeBGDrawable) { + final DrawableColorTester themeBGTester = + new DrawableColorTester(themeBGDrawable, true /* filterTransparent */); + if (themeBGTester.nonTransparentRatio() == 0) { + // the window background is transparent, unable to draw + Slog.w(TAG, "Window background is transparent, fill background with black color"); + return getSystemBGColor(); + } else { + return themeBGTester.getDominateColor(); + } + } + + SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) { + updateDensity(); + + getWindowAttrs(context, mTmpAttrs); + final StartingWindowViewBuilder builder = new StartingWindowViewBuilder(); final int animationDuration; - final Drawable iconDrawable; - if (attrs.mReplaceIcon != null) { - iconDrawable = attrs.mReplaceIcon; + Drawable iconDrawable; + if (mTmpAttrs.mReplaceIcon != null) { + iconDrawable = mTmpAttrs.mReplaceIcon; animationDuration = Math.max(0, - Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration)); + Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration)); } else { - iconDrawable = iconRes != 0 ? context.getDrawable(iconRes) - : context.getPackageManager().getDefaultActivityIcon(); + iconDrawable = mIconProvider.getIconForUI( + ai, getUserHandleForUid(ai.applicationInfo.uid)); + if (iconDrawable == null) { + iconDrawable = context.getPackageManager().getDefaultActivityIcon(); + } animationDuration = 0; } + final int themeBGColor = peekWindowBGColor(context); // TODO (b/173975965) Tracking the performance on improved splash screen. return builder .setContext(context) - .setThemeDrawable(themeBGDrawable) + .setWindowBGColor(themeBGColor) .setIconDrawable(iconDrawable) .setIconAnimationDuration(animationDuration) - .setBrandingDrawable(attrs.mBrandingImage).build(); + .setBrandingDrawable(mTmpAttrs.mBrandingImage) + .setIconBackground(mTmpAttrs.mIconBgColor).build(); + } + + private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) { + final TypedArray typedArray = context.obtainStyledAttributes( + com.android.internal.R.styleable.Window); + attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0); + attrs.mWindowBgColor = typedArray.getColor( + R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT); + attrs.mReplaceIcon = typedArray.getDrawable( + R.styleable.Window_windowSplashScreenAnimatedIcon); + attrs.mAnimationDuration = typedArray.getInt( + R.styleable.Window_windowSplashScreenAnimationDuration, 0); + attrs.mBrandingImage = typedArray.getDrawable( + R.styleable.Window_windowSplashScreenBrandingImage); + attrs.mIconBgColor = typedArray.getColor( + R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT); + typedArray.recycle(); + if (DEBUG) { + Slog.d(TAG, "window attributes color: " + + Integer.toHexString(attrs.mWindowBgColor) + + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration + + " brandImage " + attrs.mBrandingImage); + } } - private static class SplashScreenWindowAttrs { + static class SplashScreenWindowAttrs { private int mWindowBgResId = 0; private int mWindowBgColor = Color.TRANSPARENT; private Drawable mReplaceIcon = null; private Drawable mBrandingImage = null; + private int mIconBgColor = Color.TRANSPARENT; private int mAnimationDuration = 0; - - static SplashScreenWindowAttrs createWindowAttrs(Context context) { - final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs(); - final TypedArray typedArray = context.obtainStyledAttributes( - com.android.internal.R.styleable.Window); - attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0); - attrs.mWindowBgColor = typedArray.getColor( - R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT); - attrs.mReplaceIcon = typedArray.getDrawable( - R.styleable.Window_windowSplashScreenAnimatedIcon); - attrs.mAnimationDuration = typedArray.getInt( - R.styleable.Window_windowSplashScreenAnimationDuration, 0); - attrs.mBrandingImage = typedArray.getDrawable( - R.styleable.Window_windowSplashScreenBrandingImage); - typedArray.recycle(); - if (DEBUG) { - Slog.d(TAG, "window attributes color: " - + Integer.toHexString(attrs.mWindowBgColor) - + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration - + " brandImage " + attrs.mBrandingImage); - } - return attrs; - } } private class StartingWindowViewBuilder { - private Drawable mThemeBGDrawable; private Drawable mIconDrawable; private int mIconAnimationDuration; private Context mContext; private Drawable mBrandingDrawable; + private @ColorInt int mIconBackground; // result private boolean mBuildComplete = false; @@ -197,8 +222,8 @@ public class SplashscreenContentDrawer { private Drawable mFinalIconDrawable; private float mScale = 1f; - StartingWindowViewBuilder setThemeDrawable(Drawable background) { - mThemeBGDrawable = background; + StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) { + mThemeColor = background; mBuildComplete = false; return this; } @@ -221,6 +246,12 @@ public class SplashscreenContentDrawer { return this; } + StartingWindowViewBuilder setIconBackground(int color) { + mIconBackground = color; + mBuildComplete = false; + return this; + } + StartingWindowViewBuilder setContext(Context context) { mContext = context; mBuildComplete = false; @@ -235,16 +266,14 @@ public class SplashscreenContentDrawer { Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!"); return null; } - if (mThemeBGDrawable == null) { - Slog.w(TAG, "Theme Background Drawable is null, forget to set Theme Drawable?"); - mThemeBGDrawable = createDefaultBackgroundDrawable(); - } - processThemeColor(); + if (!processAdaptiveIcon() && mIconDrawable != null) { if (DEBUG) { Slog.d(TAG, "The icon is not an AdaptiveIconDrawable"); } - mFinalIconDrawable = mIconDrawable; + mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable( + mIconBackground != Color.TRANSPARENT + ? mIconBackground : mThemeColor, mIconDrawable, mIconSize); } final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0; mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable); @@ -252,16 +281,10 @@ public class SplashscreenContentDrawer { return mCachedResult; } - private void processThemeColor() { - final DrawableColorTester themeBGTester = - new DrawableColorTester(mThemeBGDrawable, true /* filterTransparent */); - if (themeBGTester.nonTransparentRatio() == 0) { - // the window background is transparent, unable to draw - Slog.w(TAG, "Window background is transparent, fill background with black color"); - mThemeColor = getSystemBGColor(); - } else { - mThemeColor = themeBGTester.getDominateColor(); - } + private void createIconDrawable(Drawable iconDrawable, int iconSize) { + mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable( + mIconBackground != Color.TRANSPARENT + ? mIconBackground : mThemeColor, iconDrawable, iconSize); } private boolean processAdaptiveIcon() { @@ -269,6 +292,7 @@ public class SplashscreenContentDrawer { return false; } + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon"); final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) mIconDrawable; final DrawableColorTester backIconTester = new DrawableColorTester(adaptiveIconDrawable.getBackground()); @@ -305,28 +329,31 @@ public class SplashscreenContentDrawer { if (DEBUG) { Slog.d(TAG, "makeSplashScreenContentView: choose fg icon"); } - // Using AdaptiveIconDrawable here can help keep the shape consistent with the - // current settings. - mFinalIconDrawable = new AdaptiveIconDrawable( - new ColorDrawable(mThemeColor), iconForeground); // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we // should enlarge the size 108/72 if we only draw adaptiveIcon's foreground. if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) { mScale = 1.5f; } + // Using AdaptiveIconDrawable here can help keep the shape consistent with the + // current settings. + final int iconSize = (int) (0.5f + mIconSize * mScale); + createIconDrawable(iconForeground, iconSize); } else { if (DEBUG) { Slog.d(TAG, "makeSplashScreenContentView: draw whole icon"); } - mFinalIconDrawable = adaptiveIconDrawable; + createIconDrawable(adaptiveIconDrawable, mIconSize); } + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return true; } private SplashScreenView fillViewWithIcon(Context context, int iconSize, Drawable iconDrawable) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon"); final SplashScreenView.Builder builder = new SplashScreenView.Builder(context); - builder.setIconSize(iconSize).setBackgroundColor(mThemeColor); + builder.setIconSize(iconSize).setBackgroundColor(mThemeColor) + .setIconBackground(mIconBackground); if (iconDrawable != null) { builder.setCenterViewDrawable(iconDrawable); } @@ -340,6 +367,7 @@ public class SplashscreenContentDrawer { Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView); } splashScreenView.makeSystemUIColorsTransparent(); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return splashScreenView; } } @@ -371,7 +399,7 @@ public class SplashscreenContentDrawer { return root < 0.1; } - private static SplashScreenView makeSplashscreenContentDrawable(Context ctx, + static SplashScreenView makeSplashscreenContent(Context ctx, int splashscreenContentResId) { // doesn't support windowSplashscreenContent after S // TODO add an allowlist to skip some packages if needed @@ -501,6 +529,7 @@ public class SplashscreenContentDrawer { new TransparentFilterQuantizer(); ComplexDrawableTester(Drawable drawable, boolean filterTransparent) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester"); final Rect initialBounds = drawable.copyBounds(); int width = drawable.getIntrinsicWidth(); int height = drawable.getIntrinsicHeight(); @@ -535,6 +564,7 @@ public class SplashscreenContentDrawer { } mPalette = builder.generate(); bitmap.recycle(); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java new file mode 100644 index 000000000000..a4a83eb87b94 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.startingsurface; + +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.annotation.ColorInt; +import android.annotation.NonNull; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Shader; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.SystemClock; +import android.os.Trace; +import android.util.PathParser; +import android.window.SplashScreenView; + +import com.android.internal.R; + +import java.util.function.Consumer; + +/** + * Creating a lightweight Drawable object used for splash screen. + * @hide + */ +public class SplashscreenIconDrawableFactory { + + static Drawable makeIconDrawable(@ColorInt int backgroundColor, + @NonNull Drawable foregroundDrawable, int iconSize) { + if (foregroundDrawable instanceof Animatable) { + return new AnimatableIconDrawable(backgroundColor, foregroundDrawable); + } else if (foregroundDrawable instanceof AdaptiveIconDrawable) { + return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize); + } else { + return new ImmobileIconDrawable(new AdaptiveIconDrawable( + new ColorDrawable(backgroundColor), foregroundDrawable), iconSize); + } + } + + private static class ImmobileIconDrawable extends Drawable { + private Shader mLayersShader; + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG + | Paint.FILTER_BITMAP_FLAG); + + ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize) { + cachePaint(drawable, iconSize, iconSize); + } + + private void cachePaint(AdaptiveIconDrawable drawable, int width, int height) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint"); + final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(layersBitmap); + drawable.setBounds(0, 0, width, height); + drawable.draw(canvas); + mLayersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP, + Shader.TileMode.CLAMP); + mPaint.setShader(mLayersShader); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + + @Override + public void draw(Canvas canvas) { + final Rect bounds = getBounds(); + canvas.drawRect(bounds, mPaint); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public int getOpacity() { + return 1; + } + } + + /** + * A lightweight AdaptiveIconDrawable which support foreground to be Animatable, and keep this + * drawable masked by config_icon_mask. + * @hide + */ + private static class AnimatableIconDrawable extends SplashScreenView.SplashscreenIconDrawable { + private static final float MASK_SIZE = AdaptiveIconDrawable.MASK_SIZE; + private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f; + private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE); + private final Rect mTmpOutRect = new Rect(); + /** + * Clip path defined in R.string.config_icon_mask. + */ + private static Path sMask; + + /** + * Scaled mask based on the view bounds. + */ + private final Path mMask; + private final Path mMaskScaleOnly; + private final Matrix mMaskMatrix; + private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Drawable mForegroundDrawable; + private Animatable mAnimatableIcon; + private Animator mIconAnimator; + private boolean mAnimationTriggered; + private long mIconAnimationStart; + + AnimatableIconDrawable(@ColorInt int backgroundColor, Drawable foregroundDrawable) { + mForegroundDrawable = foregroundDrawable; + final Resources r = Resources.getSystem(); + sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask)); + mMask = new Path(sMask); + mMaskScaleOnly = new Path(mMask); + mMaskMatrix = new Matrix(); + mPaint.setColor(backgroundColor); + mPaint.setStyle(Paint.Style.FILL); + if (mForegroundDrawable != null) { + mForegroundDrawable.setCallback(mCallback); + } + } + + @Override + protected boolean prepareAnimate(long duration, Consumer<Long> startListener) { + mAnimatableIcon = (Animatable) mForegroundDrawable; + mIconAnimator = ValueAnimator.ofInt(0, 1); + mIconAnimator.setDuration(duration); + mIconAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mIconAnimationStart = SystemClock.uptimeMillis(); + if (startListener != null) { + startListener.accept(mIconAnimationStart); + } + mAnimatableIcon.start(); + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimatableIcon.stop(); + } + + @Override + public void onAnimationCancel(Animator animation) { + mAnimatableIcon.stop(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + // do not repeat + mAnimatableIcon.stop(); + } + }); + return true; + } + + @Override + protected void onBoundsChange(Rect bounds) { + if (bounds.isEmpty()) { + return; + } + updateLayerBounds(bounds); + } + + private final Callback mCallback = new Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); + } + }; + + private void updateLayerBounds(Rect bounds) { + int cX = bounds.width() / 2; + int cY = bounds.height() / 2; + + int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2)); + int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2)); + final Rect outRect = mTmpOutRect; + outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight); + + if (mForegroundDrawable != null) { + mForegroundDrawable.setBounds(outRect); + } + // reset everything that depends on the view bounds + mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE); + sMask.transform(mMaskMatrix, mMaskScaleOnly); + invalidateSelf(); + } + + private void ensureAnimationStarted() { + if (mAnimationTriggered) { + return; + } + if (mIconAnimator != null && !mIconAnimator.isRunning()) { + mIconAnimator.start(); + } + mAnimationTriggered = true; + } + + @Override + public void draw(Canvas canvas) { + if (mMaskScaleOnly != null) { + canvas.drawPath(mMaskScaleOnly, mPaint); + } + if (mForegroundDrawable != null) { + ensureAnimationStarted(); + mForegroundDrawable.draw(canvas); + } + } + + @Override + public void setAlpha(int alpha) { + mPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + if (mForegroundDrawable != null) { + mForegroundDrawable.setColorFilter(colorFilter); + } + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java index f258286f2d17..079d68973852 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java @@ -16,35 +16,15 @@ package com.android.wm.shell.startingsurface; -import android.graphics.Rect; -import android.os.IBinder; -import android.view.SurfaceControl; -import android.window.StartingWindowInfo; - -import java.util.function.BiConsumer; /** * Interface to engage starting window feature. */ public interface StartingSurface { - /** - * Called when a task need a starting window. - */ - void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken); - /** - * Called when the content of a task is ready to show, starting window can be removed. - */ - void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, - boolean playRevealAnimation); - /** - * Called when the Task wants to copy the splash screen. - * @param taskId - */ - void copySplashScreenView(int taskId); /** - * Registers the starting window listener. - * - * @param listener The callback when need a starting window. + * Returns a binder that can be passed to an external process to manipulate starting windows. */ - void setStartingWindowListener(BiConsumer<Integer, Integer> listener); + default IStartingWindow createExternalInterface() { + return null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 14fbaacb9613..29a144fe9808 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -36,6 +36,7 @@ import android.graphics.drawable.ColorDrawable; import android.hardware.display.DisplayManager; import android.os.IBinder; import android.os.SystemClock; +import android.os.UserHandle; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -68,9 +69,6 @@ public class StartingSurfaceDrawer { private final ShellExecutor mSplashScreenExecutor; private final SplashscreenContentDrawer mSplashscreenContentDrawer; - // TODO(b/131727939) remove this when clearing ActivityRecord - private static final int REMOVE_WHEN_TIMEOUT = 2000; - public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, TransactionPool pool) { mContext = context; @@ -128,6 +126,7 @@ public class StartingSurfaceDrawer { labelRes = app.labelRes; } + final int taskId = taskInfo.taskId; Context context = mContext; // replace with the default theme if the application didn't set final int theme = windowInfo.splashScreenThemeResId != 0 @@ -149,11 +148,13 @@ public class StartingSurfaceDrawer { context = displayContext; if (theme != context.getThemeResId() || labelRes != 0) { try { - context = context.createPackageContext( - activityInfo.packageName, CONTEXT_RESTRICTED); + context = context.createPackageContextAsUser(activityInfo.packageName, + CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId)); context.setTheme(theme); } catch (PackageManager.NameNotFoundException e) { - // Ignore + Slog.w(TAG, "Failed creating package context with package name " + + activityInfo.packageName + " for user " + taskInfo.userId, e); + return; } } @@ -168,25 +169,27 @@ public class StartingSurfaceDrawer { final TypedArray typedArray = overrideContext.obtainStyledAttributes( com.android.internal.R.styleable.Window); final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0); - if (resId != 0 && overrideContext.getDrawable(resId) != null) { - // We want to use the windowBackground for the override context if it is - // available, otherwise we use the default one to make sure a themed starting - // window is displayed for the app. - if (DEBUG_SPLASH_SCREEN) { - Slog.d(TAG, "addSplashScreen: apply overrideConfig" - + taskConfig + " to starting window resId=" + resId); + try { + if (resId != 0 && overrideContext.getDrawable(resId) != null) { + // We want to use the windowBackground for the override context if it is + // available, otherwise we use the default one to make sure a themed starting + // window is displayed for the app. + if (DEBUG_SPLASH_SCREEN) { + Slog.d(TAG, "addSplashScreen: apply overrideConfig" + + taskConfig + " to starting window resId=" + resId); + } + context = overrideContext; } - context = overrideContext; + } catch (Resources.NotFoundException e) { + Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: " + + taskId, e); + return; } typedArray.recycle(); } int windowFlags = 0; - final boolean enableHardAccelerated = - (activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0; - if (enableHardAccelerated) { - windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - } + windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; final boolean[] showWallpaper = new boolean[1]; final int[] splashscreenContentResId = new int[1]; @@ -247,8 +250,6 @@ public class StartingSurfaceDrawer { params.packageName = activityInfo.packageName; params.windowAnimations = win.getWindowStyle().getResourceId( com.android.internal.R.styleable.Window_windowAnimationStyle, 0); - params.privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED; params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; // Setting as trusted overlay to let touches pass through. This is safe because this // window is controlled by the system. @@ -265,24 +266,34 @@ public class StartingSurfaceDrawer { params.setTitle("Splash Screen " + activityInfo.packageName); // TODO(b/173975965) tracking performance - final int taskId = taskInfo.taskId; SplashScreenView sView = null; try { - sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes, - splashscreenContentResId[0]); final View view = win.getDecorView(); final WindowManager wm = mContext.getSystemService(WindowManager.class); - if (postAddWindow(taskId, appToken, view, wm, params)) { + // splash screen content will be deprecated after S. + sView = SplashscreenContentDrawer.makeSplashscreenContent( + context, splashscreenContentResId[0]); + final boolean splashscreenContentCompatible = sView != null; + if (splashscreenContentCompatible) { + win.setContentView(sView); + } else { + sView = mSplashscreenContentDrawer + .makeSplashScreenContentView(context, activityInfo); win.setContentView(sView); sView.cacheRootWindow(win); } + postAddWindow(taskId, appToken, view, wm, params); } catch (RuntimeException e) { // don't crash if something else bad happens, for example a // failure loading resources because we are loading from an app // on external storage that has been unmounted. - Slog.w(TAG, " failed creating starting window", e); + Slog.w(TAG, " failed creating starting window at taskId: " + taskId, e); + sView = null; } finally { - setSplashScreenRecord(taskId, sView); + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + if (record != null) { + record.setSplashScreenView(sView); + } } } @@ -293,12 +304,8 @@ public class StartingSurfaceDrawer { TaskSnapshot snapshot) { final int taskId = startingWindowInfo.taskInfo.taskId; final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken, - snapshot, mSplashScreenExecutor, - () -> removeWindowNoAnimate(taskId)); - mSplashScreenExecutor.executeDelayed(() -> removeWindowNoAnimate(taskId), - REMOVE_WHEN_TIMEOUT); - final StartingWindowRecord tView = - new StartingWindowRecord(null/* decorView */, surface); + snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId)); + final StartingWindowRecord tView = new StartingWindowRecord(null/* decorView */, surface); mStartingWindowRecords.put(taskId, tView); } @@ -333,7 +340,7 @@ public class StartingSurfaceDrawer { ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable); } - protected boolean postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm, + protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm, WindowManager.LayoutParams params) { boolean shouldSaveView = true; try { @@ -352,11 +359,8 @@ public class StartingSurfaceDrawer { } if (shouldSaveView) { removeWindowNoAnimate(taskId); - mSplashScreenExecutor.executeDelayed( - () -> removeWindowNoAnimate(taskId), REMOVE_WHEN_TIMEOUT); saveSplashScreenRecord(taskId, view); } - return shouldSaveView; } private void saveSplashScreenRecord(int taskId, View view) { @@ -365,14 +369,6 @@ public class StartingSurfaceDrawer { mStartingWindowRecords.put(taskId, tView); } - private void setSplashScreenRecord(int taskId, SplashScreenView splashScreenView) { - final StartingWindowRecord record = mStartingWindowRecords.get(taskId); - if (record != null) { - record.setSplashScreenView(splashScreenView); - splashScreenView.startIntroAnimation(); - } - } - private void removeWindowNoAnimate(int taskId) { removeWindowSynced(taskId, null, null, false); } @@ -386,15 +382,16 @@ public class StartingSurfaceDrawer { Slog.v(TAG, "Removing splash screen window for task: " + taskId); } if (record.mContentView != null) { - final HandleExitFinish exitFinish = new HandleExitFinish(record.mDecorView); if (leash != null || playRevealAnimation) { mSplashscreenContentDrawer.applyExitAnimation(record.mContentView, - leash, frame, record.isEarlyExit(), exitFinish); - mSplashScreenExecutor.executeDelayed(exitFinish, REMOVE_WHEN_TIMEOUT); + leash, frame, record.isEarlyExit(), + () -> removeWindowInner(record.mDecorView, true)); } else { + // TODO(183004107) Always hide decorView when playRevealAnimation is enabled + // from TaskOrganizerController#removeStartingWindow // the SplashScreenView has been copied to client, skip default exit // animation - exitFinish.run(); + removeWindowInner(record.mDecorView, false); } } } @@ -408,23 +405,13 @@ public class StartingSurfaceDrawer { } } - private static class HandleExitFinish implements Runnable { - private View mDecorView; - - HandleExitFinish(View decorView) { - mDecorView = decorView; + private void removeWindowInner(View decorView, boolean hideView) { + if (hideView) { + decorView.setVisibility(View.GONE); } - - @Override - public void run() { - if (mDecorView == null) { - return; - } - final WindowManager wm = mDecorView.getContext().getSystemService(WindowManager.class); - if (wm != null) { - wm.removeView(mDecorView); - } - mDecorView = null; + final WindowManager wm = decorView.getContext().getSystemService(WindowManager.class); + if (wm != null) { + wm.removeView(decorView); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index a694e525a761..a06068d6c497 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -25,6 +25,8 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK; import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; @@ -37,6 +39,9 @@ import android.window.StartingWindowInfo; import android.window.TaskOrganizer; import android.window.TaskSnapshot; +import androidx.annotation.BinderThread; + +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; @@ -58,7 +63,7 @@ import java.util.function.BiConsumer; * constructor to keep everything synchronized. * @hide */ -public class StartingWindowController { +public class StartingWindowController implements RemoteCallable<StartingWindowController> { private static final String TAG = StartingWindowController.class.getSimpleName(); static final boolean DEBUG_SPLASH_SCREEN = false; static final boolean DEBUG_TASK_SNAPSHOT = false; @@ -68,17 +73,17 @@ public class StartingWindowController { private BiConsumer<Integer, Integer> mTaskLaunchingCallback; private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl(); + private final Context mContext; private final ShellExecutor mSplashScreenExecutor; // For Car Launcher public StartingWindowController(Context context, ShellExecutor splashScreenExecutor) { - mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, - new TransactionPool()); - mSplashScreenExecutor = splashScreenExecutor; + this(context, splashScreenExecutor, new TransactionPool()); } public StartingWindowController(Context context, ShellExecutor splashScreenExecutor, TransactionPool pool) { + mContext = context; mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool); mSplashScreenExecutor = splashScreenExecutor; } @@ -90,6 +95,16 @@ public class StartingWindowController { return mImpl; } + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mSplashScreenExecutor; + } + private static class StartingTypeChecker { TaskSnapshot mSnapshot; @@ -150,6 +165,13 @@ public class StartingWindowController { } return false; } + if (!snapshot.getTopActivityComponent().equals(windowInfo.taskInfo.topActivity)) { + if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { + Slog.d(TAG, "isSnapshotCompatible obsoleted snapshot " + + windowInfo.taskInfo.topActivity); + } + return false; + } final int taskRotation = windowInfo.taskInfo.configuration .windowConfiguration.getRotation(); @@ -188,59 +210,121 @@ public class StartingWindowController { /** * Called when a task need a starting window. */ - void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { - final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo); - final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; - if (mTaskLaunchingCallback != null) { - mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType); - } - if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) { - mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken); - } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) { - final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot; - mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot); - } - // If prefer don't show, then don't show! + public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { + mSplashScreenExecutor.execute(() -> { + final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo); + final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; + if (mTaskLaunchingCallback != null) { + mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType); + } + if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) { + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken); + } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) { + final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot; + mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot); + } + // If prefer don't show, then don't show! + }); } - void copySplashScreenView(int taskId) { - mStartingSurfaceDrawer.copySplashScreenView(taskId); + public void copySplashScreenView(int taskId) { + mSplashScreenExecutor.execute(() -> { + mStartingSurfaceDrawer.copySplashScreenView(taskId); + }); } /** * Called when the content of a task is ready to show, starting window can be removed. */ - void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, boolean playRevealAnimation) { - mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation); + mSplashScreenExecutor.execute(() -> { + mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation); + }); } + /** + * The interface for calls from outside the Shell, within the host process. + */ private class StartingSurfaceImpl implements StartingSurface { + private IStartingWindowImpl mIStartingWindow; @Override - public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { - mSplashScreenExecutor.execute(() -> - StartingWindowController.this.addStartingWindow(windowInfo, appToken)); + public IStartingWindowImpl createExternalInterface() { + if (mIStartingWindow != null) { + mIStartingWindow.invalidate(); + } + mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this); + return mIStartingWindow; } + } - @Override - public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, - boolean playRevealAnimation) { - mSplashScreenExecutor.execute(() -> - StartingWindowController.this.removeStartingWindow(taskId, leash, frame, - playRevealAnimation)); + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IStartingWindowImpl extends IStartingWindow.Stub { + private StartingWindowController mController; + private IStartingWindowListener mListener; + private final BiConsumer<Integer, Integer> mStartingWindowListener = + this::notifyIStartingWindowListener; + private final IBinder.DeathRecipient mListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + final StartingWindowController controller = mController; + controller.getRemoteCallExecutor().execute(() -> { + mListener = null; + controller.setStartingWindowListener(null); + }); + } + }; + + public IStartingWindowImpl(StartingWindowController controller) { + mController = controller; } - @Override - public void copySplashScreenView(int taskId) { - mSplashScreenExecutor.execute(() -> - StartingWindowController.this.copySplashScreenView(taskId)); + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; } @Override - public void setStartingWindowListener(BiConsumer<Integer, Integer> listener) { - mSplashScreenExecutor.execute(() -> - StartingWindowController.this.setStartingWindowListener(listener)); + public void setStartingWindowListener(IStartingWindowListener listener) { + executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener", + (controller) -> { + if (mListener != null) { + // Reset the old death recipient + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + if (listener != null) { + try { + listener.asBinder().linkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } + } + mListener = listener; + controller.setStartingWindowListener(mStartingWindowListener); + }); + } + + private void notifyIStartingWindowListener(int taskId, int supportedType) { + if (mListener == null) { + return; + } + + try { + mListener.onTaskLaunching(taskId, supportedType); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify task launching", e); + } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 629ff0db6b4a..b29b18bec032 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -96,6 +96,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { }; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getMode() == TRANSIT_CHANGE) { + // No default animation for this, so just update bounds/position. + t.setPosition(change.getLeash(), + change.getEndAbsBounds().left - change.getEndRelOffset().x, + change.getEndAbsBounds().top - change.getEndRelOffset().y); + if (change.getTaskInfo() != null) { + // Skip non-tasks since those usually have null bounds. + t.setWindowCrop(change.getLeash(), + change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); + } + } // Don't animate anything that isn't independent. if (!TransitionInfo.isIndependent(change, info)) continue; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl new file mode 100644 index 000000000000..dffc700a3690 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import android.window.IRemoteTransition; +import android.window.TransitionFilter; + +/** + * Interface that is exposed to remote callers to manipulate the transitions feature. + */ +interface IShellTransitions { + + /** + * Registers a remote transition handler. + */ + oneway void registerRemote(in TransitionFilter filter, + in IRemoteTransition remoteTransition) = 1; + + /** + * Unregisters a remote transition handler. + */ + oneway void unregisterRemote(in IRemoteTransition remoteTransition) = 2; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java new file mode 100644 index 000000000000..71fd917275ea --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.SurfaceControl; +import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +/** + * Handler that forwards to a RemoteTransition. It is designed for one-shot use to attach a + * specific remote animation to a specific transition. + */ +public class OneShotRemoteHandler implements Transitions.TransitionHandler { + private final ShellExecutor mMainExecutor; + + /** The specific transition that this handler is associated with. Just for validation. */ + private IBinder mTransition = null; + + /** The remote to delegate animation to */ + private final IRemoteTransition mRemote; + + public OneShotRemoteHandler(@NonNull ShellExecutor mainExecutor, + @NonNull IRemoteTransition remote) { + mMainExecutor = mainExecutor; + mRemote = remote; + } + + public void setTransition(@NonNull IBinder transition) { + mTransition = transition; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mTransition != transition) return false; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote" + + " transition %s for %s.", mRemote, transition); + + final IBinder.DeathRecipient remoteDied = () -> { + Log.e(Transitions.TAG, "Remote transition died, finishing"); + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + }; + IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { + @Override + public void onTransitionFinished(WindowContainerTransaction wct) { + if (mRemote.asBinder() != null) { + mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); + } + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(wct, null /* wctCB */)); + } + }; + try { + if (mRemote.asBinder() != null) { + mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */); + } + mRemote.startAnimation(info, t, cb); + } catch (RemoteException e) { + if (mRemote.asBinder() != null) { + mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); + } + Log.e(Transitions.TAG, "Error running remote transition.", e); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + } + return true; + } + + @Override + @Nullable + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @Nullable TransitionRequestInfo request) { + IRemoteTransition remote = request.getRemoteTransition(); + if (remote != mRemote) return null; + mTransition = transition; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested" + + " for %s: %s", transition, remote); + return new WindowContainerTransaction(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index ac93a17b4014..8876e5f86139 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -16,6 +16,8 @@ package com.android.wm.shell.transition; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; @@ -23,6 +25,7 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; +import android.util.Slog; import android.view.SurfaceControl; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; @@ -31,6 +34,8 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; +import androidx.annotation.BinderThread; + import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -42,6 +47,8 @@ import java.util.ArrayList; * if the request includes a specific remote. */ public class RemoteTransitionHandler implements Transitions.TransitionHandler { + private static final String TAG = "RemoteTransitionHandler"; + private final ShellExecutor mMainExecutor; /** Includes remotes explicitly requested by, eg, ActivityOptions */ @@ -51,20 +58,42 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { private final ArrayList<Pair<TransitionFilter, IRemoteTransition>> mFilters = new ArrayList<>(); + private final IBinder.DeathRecipient mTransitionDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + mMainExecutor.execute(() -> { + mFilters.clear(); + }); + } + }; + RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) { mMainExecutor = mainExecutor; } void addFiltered(TransitionFilter filter, IRemoteTransition remote) { + try { + remote.asBinder().linkToDeath(mTransitionDeathRecipient, 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } mFilters.add(new Pair<>(filter, remote)); } void removeFiltered(IRemoteTransition remote) { + boolean removed = false; for (int i = mFilters.size() - 1; i >= 0; --i) { if (mFilters.get(i).second == remote) { mFilters.remove(i); + removed = true; } } + if (removed) { + remote.asBinder().unlinkToDeath(mTransitionDeathRecipient, 0 /* flags */); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java index 85bbf74d56b9..bc42c6e2f12c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java @@ -26,7 +26,15 @@ import com.android.wm.shell.common.annotations.ExternalThread; * Interface to manage remote transitions. */ @ExternalThread -public interface RemoteTransitions { +public interface ShellTransitions { + + /** + * Returns a binder that can be passed to an external process to manipulate remote transitions. + */ + default IShellTransitions createExternalInterface() { + return null; + } + /** * Registers a remote transition. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 677db10d07a7..ca1b53d4d46b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -23,6 +23,8 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; @@ -51,6 +53,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; @@ -60,7 +63,7 @@ import java.util.ArrayList; import java.util.Arrays; /** Plays transition animations */ -public class Transitions { +public class Transitions implements RemoteCallable<Transitions> { static final String TAG = "ShellTransitions"; /** Set to {@code true} to enable shell transitions. */ @@ -73,7 +76,7 @@ public class Transitions { private final ShellExecutor mAnimExecutor; private final TransitionPlayerImpl mPlayerImpl; private final RemoteTransitionHandler mRemoteTransitionHandler; - private final RemoteTransitionImpl mImpl = new RemoteTransitionImpl(); + private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); @@ -87,10 +90,6 @@ public class Transitions { /** Keeps track of currently tracked transitions and all the animations associated with each */ private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>(); - public static RemoteTransitions asRemoteTransitions(Transitions transitions) { - return transitions.mImpl; - } - public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull Context context, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { @@ -126,6 +125,20 @@ public class Transitions { mRemoteTransitionHandler = null; } + public ShellTransitions asRemoteTransitions() { + return mImpl; + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + private void dispatchAnimScaleSetting(float scale) { for (int i = mHandlers.size() - 1; i >= 0; --i) { mHandlers.get(i).setAnimScaleSetting(scale); @@ -134,8 +147,8 @@ public class Transitions { /** Create an empty/non-registering transitions object for system-ui tests. */ @VisibleForTesting - public static RemoteTransitions createEmptyForTesting() { - return new RemoteTransitions() { + public static ShellTransitions createEmptyForTesting() { + return new ShellTransitions() { @Override public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter, @androidx.annotation.NonNull IRemoteTransition remoteTransition) { @@ -426,24 +439,74 @@ public class Transitions { } } + /** + * The interface for calls from outside the Shell, within the host process. + */ @ExternalThread - private class RemoteTransitionImpl implements RemoteTransitions { + private class ShellTransitionImpl implements ShellTransitions { + private IShellTransitionsImpl mIShellTransitions; + + @Override + public IShellTransitions createExternalInterface() { + if (mIShellTransitions != null) { + mIShellTransitions.invalidate(); + } + mIShellTransitions = new IShellTransitionsImpl(Transitions.this); + return mIShellTransitions; + } + @Override public void registerRemote(@NonNull TransitionFilter filter, @NonNull IRemoteTransition remoteTransition) { mMainExecutor.execute(() -> { - Transitions.this.registerRemote(filter, remoteTransition); + mRemoteTransitionHandler.addFiltered(filter, remoteTransition); }); } @Override public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) { mMainExecutor.execute(() -> { - Transitions.this.unregisterRemote(remoteTransition); + mRemoteTransitionHandler.removeFiltered(remoteTransition); }); } } + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IShellTransitionsImpl extends IShellTransitions.Stub { + private Transitions mTransitions; + + IShellTransitionsImpl(Transitions transitions) { + mTransitions = transitions; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mTransitions = null; + } + + @Override + public void registerRemote(@NonNull TransitionFilter filter, + @NonNull IRemoteTransition remoteTransition) { + executeRemoteCallWithTaskPermission(mTransitions, "registerRemote", + (transitions) -> { + transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition); + }); + } + + @Override + public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) { + executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote", + (transitions) -> { + transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition); + }); + } + } + private class SettingsObserver extends ContentObserver { SettingsObserver() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index 35bab7aaf22c..fcd333f13868 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -73,7 +73,8 @@ fun FlickerTestParameter.dockedStackDividerIsInvisible() { fun FlickerTestParameter.appPairsPrimaryBoundsIsVisible(rotation: Int, primaryLayerName: String) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.coversExactly(getPrimaryRegion(dividerRegion, rotation), primaryLayerName) + visibleRegion(primaryLayerName) + .coversExactly(getPrimaryRegion(dividerRegion, rotation)) } } @@ -83,7 +84,8 @@ fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER) - this.coversExactly(getPrimaryRegion(dividerRegion, rotation), primaryLayerName) + visibleRegion(primaryLayerName) + .coversExactly(getPrimaryRegion(dividerRegion, rotation)) } } @@ -93,7 +95,8 @@ fun FlickerTestParameter.appPairsSecondaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.coversExactly(getSecondaryRegion(dividerRegion, rotation), secondaryLayerName) + visibleRegion(secondaryLayerName) + .coversExactly(getSecondaryRegion(dividerRegion, rotation)) } } @@ -103,7 +106,8 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER) - this.coversExactly(getSecondaryRegion(dividerRegion, rotation), secondaryLayerName) + visibleRegion(secondaryLayerName) + .coversExactly(getSecondaryRegion(dividerRegion, rotation)) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt deleted file mode 100644 index 1869d833fbc4..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("Utils") -package com.android.wm.shell.flicker - -import android.app.ActivityTaskManager -import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT -import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS -import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD -import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED - -fun removeAllTasksButHome() { - val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf( - ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, - ACTIVITY_TYPE_UNDEFINED) - val atm = ActivityTaskManager.getService() - atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME) -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index 90e71373b1fd..98ce2747d532 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -18,6 +18,8 @@ package com.android.wm.shell.flicker.apppairs import android.os.SystemClock import android.platform.test.annotations.Presubmit +import android.provider.Settings +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -25,6 +27,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.appPairsDividerIsInvisible import com.android.wm.shell.flicker.helpers.AppPairsHelper +import org.junit.After +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -32,11 +36,10 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test AppPairs launch. - * To run this test: `atest WMShellFlickerTests:AppPairsTest` - */ -/** - * Test cold launch app from launcher. + * Test cold launch app from launcher. When the device doesn't support non-resizable in multi window + * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs should not pair + * non-resizable apps. + * * To run this test: `atest WMShellFlickerTests:AppPairsTestCannotPairNonResizeableApps` */ @RequiresDevice @@ -46,6 +49,7 @@ import org.junit.runners.Parameterized class AppPairsTestCannotPairNonResizeableApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -59,6 +63,32 @@ class AppPairsTestCannotPairNonResizeableApps( } } + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 1) { + // Not support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index dc51b4fb5a9e..614530b5e469 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -56,6 +56,14 @@ class AppPairsTestPairPrimaryAndSecondaryApps( } } + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() @@ -74,10 +82,10 @@ class AppPairsTestPairPrimaryAndSecondaryApps( fun appsEndingBounds() { testSpec.assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion), - primaryApp.defaultWindowName) - .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion), - secondaryApp.defaultWindowName) + visibleRegion(primaryApp.defaultWindowName) + .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion)) + visibleRegion(secondaryApp.defaultWindowName) + .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion)) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt new file mode 100644 index 000000000000..1e3595c17f48 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.apppairs + +import android.os.SystemClock +import android.platform.test.annotations.Presubmit +import android.provider.Settings +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.appPairsDividerIsVisible +import com.android.wm.shell.flicker.helpers.AppPairsHelper +import org.junit.After +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launch app from launcher. When the device supports non-resizable in multi window + * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs can pair + * non-resizable apps. + * + * To run this test: `atest WMShellFlickerTests:AppPairsTestSupportPairNonResizeableApps` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class AppPairsTestSupportPairNonResizeableApps( + testSpec: FlickerTestParameter +) : AppPairsTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + transitions { + nonResizeableApp?.launchViaIntent(wmHelper) + // TODO pair apps through normal UX flow + executeShellCommand( + composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true)) + SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + } + } + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 0) { + // Support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + @Presubmit + @Test + fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() + + @Presubmit + @Test + fun bothAppWindowVisible() { + val nonResizeableApp = nonResizeableApp + require(nonResizeableApp != null) { + "Non resizeable app not initialized" + } + testSpec.assertWmEnd { + isVisible(nonResizeableApp.defaultWindowName) + isVisible(primaryApp.defaultWindowName) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = AppPairsHelper.TEST_REPETITIONS) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 5bb9b2f8b8ca..ef68ed630353 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -61,6 +61,14 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( } } + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible() @@ -79,10 +87,10 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( fun appsStartingBounds() { testSpec.assertLayersStart { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion), - primaryApp.defaultWindowName) - coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion), - secondaryApp.defaultWindowName) + visibleRegion(primaryApp.defaultWindowName) + .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion)) + visibleRegion(secondaryApp.defaultWindowName) + .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion)) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index 91e080f65550..134d00be73e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -17,22 +17,27 @@ package com.android.wm.shell.flicker.apppairs import android.app.Instrumentation +import android.content.Context import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper import android.util.Log +import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.compatibility.common.util.SystemUtil import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.isRotated import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -42,6 +47,7 @@ import java.io.IOException abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected val context: Context = instrumentation.context protected val isRotated = testSpec.config.startRotation.isRotated() protected val activityHelper = ActivityHelper.getInstance() protected val appPairsHelper = AppPairsHelper(instrumentation, @@ -134,17 +140,39 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) @Presubmit @Test - open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + open fun navBarLayerIsAlwaysVisible() { + testSpec.navBarLayerIsAlwaysVisible() + } + + @Presubmit + @Test + open fun statusBarLayerIsAlwaysVisible() { + testSpec.statusBarLayerIsAlwaysVisible() + } + + @Presubmit + @Test + open fun navBarWindowIsAlwaysVisible() { + testSpec.navBarWindowIsAlwaysVisible() + } @Presubmit @Test - open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + open fun statusBarWindowIsAlwaysVisible() { + testSpec.statusBarWindowIsAlwaysVisible() + } @Presubmit @Test - open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + open fun navBarLayerRotatesAndScales() { + testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, + testSpec.config.endRotation) + } @Presubmit @Test - open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + open fun statusBarLayerRotatesScales() { + testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, + testSpec.config.endRotation) + } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index 5f003ba62b2d..d341bb1e6aa9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -27,8 +27,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.wm.shell.flicker.appPairsDividerIsVisible import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible @@ -75,16 +73,6 @@ class RotateTwoLaunchedAppsInAppPairsMode( @Test fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, - testSpec.config.endRotation) - - @Presubmit - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, - testSpec.config.endRotation) - @FlakyTest(bugId = 172776659) @Test fun appPairsPrimaryBoundsIsVisible() = diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index d4792088ac31..3bf0296fee20 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -27,16 +27,13 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.appPairsDividerIsVisible import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -67,20 +64,10 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, - testSpec.config.endRotation) - - @Presubmit - @Test fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() @Presubmit @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales( - Surface.ROTATION_0, testSpec.config.endRotation) - - @Presubmit - @Test override fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index 5a96a7c8cbd9..f4dd7decb1b7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -26,8 +26,6 @@ import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild -import com.android.wm.shell.flicker.pip.waitPipWindowGone -import com.android.wm.shell.flicker.pip.waitPipWindowShown import com.android.wm.shell.flicker.testapp.Components class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( @@ -64,7 +62,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( stringExtras: Map<String, String> ) { super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras) - wmHelper.waitPipWindowShown() + wmHelper.waitFor { it.wmState.hasPipWindow() } } private fun focusOnObject(selector: BySelector): Boolean { @@ -86,7 +84,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( clickObject(ENTER_PIP_BUTTON_ID) // Wait on WMHelper or simply wait for 3 seconds - wmHelper?.waitPipWindowShown() ?: SystemClock.sleep(3_000) + wmHelper?.waitFor { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000) } fun clickStartMediaSessionButton() { @@ -139,7 +137,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( } // Wait for animation to complete. - wmHelper.waitPipWindowGone() + wmHelper.waitFor { !it.wmState.hasPipWindow() } wmHelper.waitForHomeActivityVisible() } @@ -169,7 +167,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( val windowRect = windowRegion.bounds uiDevice.click(windowRect.centerX(), windowRect.centerY()) uiDevice.click(windowRect.centerX(), windowRect.centerY()) - wmHelper.waitPipWindowGone() + wmHelper.waitFor { !it.wmState.hasPipWindow() } wmHelper.waitForAppTransitionIdle() } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt index 17c51fb15b0c..bca257646e11 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt @@ -29,8 +29,7 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -60,6 +59,11 @@ class EnterSplitScreenDockActivity( } } + override val ignoredWindows: List<String> + get() = listOf(LAUNCHER_PACKAGE_NAME, WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME, + splitScreenApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + @FlakyTest(bugId = 169271943) @Test fun dockedStackPrimaryBoundsIsVisible() = @@ -73,12 +77,8 @@ class EnterSplitScreenDockActivity( @FlakyTest(bugId = 178531736) @Test // b/178531736 - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, - WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME, - splitScreenApp.defaultWindowName) - ) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Presubmit @Test @@ -91,12 +91,8 @@ class EnterSplitScreenDockActivity( @FlakyTest(bugId = 178531736) @Test // b/178531736 - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, - WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME, - splitScreenApp.defaultWindowName) - ) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt index a94fd463c624..9000f22fb03d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt @@ -30,8 +30,7 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible @@ -62,6 +61,11 @@ class EnterSplitScreenLaunchToSide( } } + override val ignoredWindows: List<String> + get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, + secondaryApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + @FlakyTest(bugId = 169271943) @Test fun dockedStackPrimaryBoundsIsVisible() = @@ -83,12 +87,8 @@ class EnterSplitScreenLaunchToSide( @Test // TODO(b/178447631) Remove Splash Screen from white list when flicker lib // add a wait for splash screen be gone - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME, - splitScreenApp.defaultWindowName, - secondaryApp.defaultWindowName) - ) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Presubmit @Test @@ -104,12 +104,8 @@ class EnterSplitScreenLaunchToSide( @Presubmit @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME, - splitScreenApp.defaultWindowName, - secondaryApp.defaultWindowName) - ) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt index 238059b484b5..7d22d4dbe5ab 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt @@ -16,21 +16,23 @@ package com.android.wm.shell.flicker.legacysplitscreen +import android.platform.test.annotations.Postsubmit +import android.provider.Settings import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.WALLPAPER_TITLE import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.canSplitScreen import com.android.server.wm.flicker.helpers.openQuickstep -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.dockedStackDividerIsInvisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import org.junit.After import org.junit.Assert +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -38,27 +40,31 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test open non-resizable activity will auto exit split screen mode - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNonResizableNotDock` + * Test enter split screen from non-resizable activity. When the device doesn't support + * non-resizable in multi window, there should be no button to enter split screen for non-resizable + * activity. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNotSupportNonResizable` */ +@Postsubmit @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FlakyTest(bugId = 173875043) -class EnterSplitScreenNonResizableNotDock( +class EnterSplitScreenNotSupportNonResizable( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> - super.transition(this, configuration) - teardown { + cleanSetup(this, configuration) + setup { eachRun { - nonResizeableApp.exit(wmHelper) + nonResizeableApp.launchViaIntent(wmHelper) } } transitions { - nonResizeableApp.launchViaIntent(wmHelper) device.openQuickstep(wmHelper) if (device.canSplitScreen(wmHelper)) { Assert.fail("Non-resizeable app should not enter split screen") @@ -66,28 +72,42 @@ class EnterSplitScreenNonResizableNotDock( } } - @Test - fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible() + override val ignoredWindows: List<String> + get() = listOf(LAUNCHER_PACKAGE_NAME, + WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, + nonResizeableApp.defaultWindowName, + splitScreenApp.defaultWindowName) + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 1) { + // Not support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } @FlakyTest(bugId = 178447631) @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, - SPLASH_SCREEN_NAME, - nonResizeableApp.defaultWindowName, - splitScreenApp.defaultWindowName) - ) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry( - listOf(WALLPAPER_TITLE, - LAUNCHER_PACKAGE_NAME, - SPLASH_SCREEN_NAME, - nonResizeableApp.defaultWindowName, - splitScreenApp.defaultWindowName) - ) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + @Test + fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible() @Test fun appWindowIsVisible() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt new file mode 100644 index 000000000000..9b4a10389619 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.legacysplitscreen + +import android.platform.test.annotations.Postsubmit +import android.provider.Settings +import android.view.Surface +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.launchSplitScreen +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.wm.shell.flicker.dockedStackDividerIsVisible +import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import org.junit.After +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test enter split screen from non-resizable activity. When the device supports + * non-resizable in multi window, there should be a button to enter split screen for non-resizable + * activity. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenSupportNonResizable` + */ +@Postsubmit +@RequiresDevice +@RunWith(Parameterized::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +class EnterSplitScreenSupportNonResizable( + testSpec: FlickerTestParameter +) : LegacySplitScreenTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { configuration -> + cleanSetup(this, configuration) + setup { + eachRun { + nonResizeableApp.launchViaIntent(wmHelper) + } + } + transitions { + device.launchSplitScreen(wmHelper) + } + } + + override val ignoredWindows: List<String> + get() = listOf(LAUNCHER_PACKAGE_NAME, + WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, + nonResizeableApp.defaultWindowName, + splitScreenApp.defaultWindowName) + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow != 1) { + // Support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest(bugId = 178447631) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + @Test + fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible() + + @Test + fun appWindowIsVisible() { + testSpec.assertWmEnd { + isVisible(nonResizeableApp.defaultWindowName) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = SplitScreenHelper.TEST_REPETITIONS, + supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt index acd570a3773e..9717709852d4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt @@ -30,8 +30,7 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder @@ -70,17 +69,20 @@ class ExitLegacySplitScreenFromBottom( } } + override val ignoredWindows: List<String> + get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME, + splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + @Presubmit @Test fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(DOCKED_STACK_DIVIDER) @FlakyTest(bugId = 178447631) @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, - secondaryApp.defaultWindowName) - ) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } @Presubmit @Test @@ -97,11 +99,8 @@ class ExitLegacySplitScreenFromBottom( @FlakyTest(bugId = 178447631) @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, - secondaryApp.defaultWindowName) - ) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt index cef188695ce7..3f714bb6b6c9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt @@ -30,8 +30,7 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.dockedStackDividerIsInvisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder @@ -70,6 +69,11 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen( } } + override val ignoredWindows: List<String> + get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME, + splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + @FlakyTest(bugId = 175687842) @Test fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible() @@ -80,11 +84,8 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen( @FlakyTest(bugId = 178447631) @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, - secondaryApp.defaultWindowName) - ) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Presubmit @Test @@ -101,11 +102,8 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen( @FlakyTest(bugId = 178447631) @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, - secondaryApp.defaultWindowName) - ) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt new file mode 100644 index 000000000000..892384561eb2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.legacysplitscreen + +import android.platform.test.annotations.Postsubmit +import android.provider.Settings +import android.view.Surface +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.appWindowBecomesInVisible +import com.android.server.wm.flicker.appWindowBecomesVisible +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.launchSplitScreen +import com.android.server.wm.flicker.layerBecomesInvisible +import com.android.server.wm.flicker.layerBecomesVisible +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER +import com.android.wm.shell.flicker.dockedStackDividerIsInvisible +import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import org.junit.After +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launch non-resizable activity via intent in split screen mode. When the device does not + * support non-resizable in multi window, it should trigger exit split screen. + * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentNotSupportNonResizable` + */ +@Postsubmit +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class LegacySplitScreenFromIntentNotSupportNonResizable( + testSpec: FlickerTestParameter +) : LegacySplitScreenTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { configuration -> + cleanSetup(this, configuration) + setup { + eachRun { + splitScreenApp.launchViaIntent(wmHelper) + device.launchSplitScreen(wmHelper) + } + } + transitions { + nonResizeableApp.launchViaIntent(wmHelper) + wmHelper.waitForAppTransitionIdle() + } + } + + override val ignoredWindows: List<String> + get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, + nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName, + WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 1) { + // Not support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest(bugId = 178447631) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @FlakyTest(bugId = 178447631) + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + @Test + fun resizableAppLayerBecomesInvisible() = + testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) + + @Test + fun nonResizableAppLayerBecomesVisible() = + testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) + + @Test + fun resizableAppWindowBecomesInvisible() = + testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName) + + @Test + fun nonResizableAppWindowBecomesVisible() = + testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) + + @Test + fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible() + + @Test + fun onlyNonResizableAppWindowIsVisibleAtEnd() { + testSpec.assertWmEnd { + isInvisible(splitScreenApp.defaultWindowName) + isVisible(nonResizeableApp.defaultWindowName) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = SplitScreenHelper.TEST_REPETITIONS, + supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt index 543484ac9759..2f5e0bddd71f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt @@ -16,23 +16,24 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.Postsubmit +import android.provider.Settings import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.appWindowBecomesInVisible import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.layerBecomesVisible -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER +import com.android.wm.shell.flicker.dockedStackDividerIsVisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import org.junit.After +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -40,18 +41,20 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test launch non resizable activity in split screen mode will trigger exit split screen mode - * (Non resizable activity launch via intent) - * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreen` + * Test launch non-resizable activity via intent in split screen mode. When the device supports + * non-resizable in multi window, it should show the non-resizable app in split screen. + * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentSupportNonResizable` */ -@Presubmit +@Postsubmit @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class NonResizableLaunchInLegacySplitScreen( +class LegacySplitScreenFromIntentSupportNonResizable( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> cleanSetup(this, configuration) @@ -67,45 +70,58 @@ class NonResizableLaunchInLegacySplitScreen( } } - @Presubmit - @Test - fun layerBecomesVisible() = testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) + override val ignoredWindows: List<String> + get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, + nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName, + WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 0) { + // Support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } - @Presubmit + @FlakyTest(bugId = 178447631) @Test - fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @FlakyTest(bugId = 178447631) @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(DOCKED_STACK_DIVIDER, - LAUNCHER_PACKAGE_NAME, - LETTERBOX_NAME, - nonResizeableApp.defaultWindowName, - splitScreenApp.defaultWindowName) - ) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Presubmit @Test - fun appWindowBecomesVisible() = - testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppLayerBecomesVisible() = + testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) - @Presubmit @Test - fun appWindowBecomesInVisible() = - testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName) + fun nonResizableAppWindowBecomesVisible() = + testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) + + @Test + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible() - @FlakyTest(bugId = 178447631) @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry( - listOf(DOCKED_STACK_DIVIDER, - LAUNCHER_PACKAGE_NAME, - LETTERBOX_NAME, - nonResizeableApp.defaultWindowName, - splitScreenApp.defaultWindowName) - ) + fun bothAppsWindowsAreVisibleAtEnd() { + testSpec.assertWmEnd { + isVisible(splitScreenApp.defaultWindowName) + isVisible(nonResizeableApp.defaultWindowName) + } + } companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt new file mode 100644 index 000000000000..a42774d93b5b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.legacysplitscreen + +import android.platform.test.annotations.Postsubmit +import android.provider.Settings +import android.view.Surface +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.appWindowBecomesInVisible +import com.android.server.wm.flicker.appWindowBecomesVisible +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.launchSplitScreen +import com.android.server.wm.flicker.helpers.reopenAppFromOverview +import com.android.server.wm.flicker.layerBecomesInvisible +import com.android.server.wm.flicker.layerBecomesVisible +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER +import com.android.wm.shell.flicker.dockedStackDividerIsInvisible +import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import org.junit.After +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launch non-resizable activity via recent overview in split screen mode. When the device does + * not support non-resizable in multi window, it should trigger exit split screen. + * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentNotSupportNonResizable` + */ +@Postsubmit +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class LegacySplitScreenFromRecentNotSupportNonResizable( + testSpec: FlickerTestParameter +) : LegacySplitScreenTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { configuration -> + cleanSetup(this, configuration) + setup { + eachRun { + nonResizeableApp.launchViaIntent(wmHelper) + splitScreenApp.launchViaIntent(wmHelper) + device.launchSplitScreen(wmHelper) + } + } + transitions { + device.reopenAppFromOverview(wmHelper) + } + } + + override val ignoredWindows: List<String> + get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME, + splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName, + WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 1) { + // Not support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest(bugId = 178447631) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @FlakyTest(bugId = 178447631) + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + @Test + fun resizableAppLayerBecomesInvisible() = + testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) + + @Test + fun nonResizableAppLayerBecomesVisible() = + testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) + + @Test + fun resizableAppWindowBecomesInvisible() = + testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName) + + @Test + fun nonResizableAppWindowBecomesVisible() = + testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) + + @Test + fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible() + + @Test + fun onlyNonResizableAppWindowIsVisibleAtEnd() { + testSpec.assertWmEnd { + isInvisible(splitScreenApp.defaultWindowName) + isVisible(nonResizeableApp.defaultWindowName) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = SplitScreenHelper.TEST_REPETITIONS, + supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt index caafa278d297..14f6deef6ff4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt @@ -16,24 +16,25 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.Postsubmit +import android.provider.Settings import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.appWindowBecomesInVisible import com.android.server.wm.flicker.appWindowBecomesVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.layerBecomesVisible -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER +import com.android.wm.shell.flicker.dockedStackDividerIsVisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import org.junit.After +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -41,17 +42,20 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test launch non resizable activity in split screen mode will trigger exit split screen mode - * (Non resizable activity launch via recent overview) - * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreen` + * Test launch non-resizable activity via recent overview in split screen mode. When the device + * supports non-resizable in multi window, it should show the non-resizable app in split screen. + * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentSupportNonResizable` */ +@Postsubmit @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class NonResizableDismissInLegacySplitScreen( +class LegacySplitScreenFromRecentSupportNonResizable( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> cleanSetup(this, configuration) @@ -67,43 +71,58 @@ class NonResizableDismissInLegacySplitScreen( } } - @Presubmit + override val ignoredWindows: List<String> + get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME, + splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName, + WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 0) { + // Support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest(bugId = 178447631) @Test - fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @FlakyTest(bugId = 178447631) @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, - LETTERBOX_NAME, TOAST_NAME, - splitScreenApp.defaultWindowName, - nonResizeableApp.defaultWindowName) - ) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Presubmit @Test - fun layerBecomesVisible() = testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) + fun nonResizableAppLayerBecomesVisible() = + testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) - @Presubmit @Test - fun appWindowBecomesVisible() = + fun nonResizableAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName) - @Presubmit @Test - fun appWindowBecomesInVisible() = - testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName) + fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible() - @FlakyTest(bugId = 178447631) @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry( - listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, - LETTERBOX_NAME, TOAST_NAME, - splitScreenApp.defaultWindowName, - nonResizeableApp.defaultWindowName) - ) + fun bothAppsWindowsAreVisibleAtEnd() { + testSpec.assertWmEnd { + isVisible(splitScreenApp.defaultWindowName) + isVisible(nonResizeableApp.defaultWindowName) + } + } companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt index 1e89a25c06df..08d5db0f9124 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt @@ -17,11 +17,13 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.view.Surface +import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import org.junit.Test abstract class LegacySplitScreenRotateTransition( testSpec: FlickerTestParameter @@ -44,4 +46,16 @@ abstract class LegacySplitScreenRotateTransition( } } } + + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + @FlakyTest + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index 7f69a66e6e82..72d6f569ab0c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -34,14 +34,13 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible import com.android.wm.shell.flicker.helpers.SimpleAppHelper import org.junit.FixMethodOrder @@ -89,6 +88,10 @@ class LegacySplitScreenToLauncher( } } + override val ignoredWindows: List<String> + get() = listOf(launcherPackageName, WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() @@ -99,11 +102,6 @@ class LegacySplitScreenToLauncher( @Presubmit @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() @Presubmit @@ -122,8 +120,8 @@ class LegacySplitScreenToLauncher( @Presubmit @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(listOf(launcherPackageName)) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt index 91ea8716e4f0..e13056c36684 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt @@ -17,6 +17,8 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.app.Instrumentation +import android.content.Context +import android.platform.test.annotations.Presubmit import android.support.test.launcherhelper.LauncherStrategyFactory import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry @@ -29,10 +31,13 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import org.junit.Test abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected val context: Context = instrumentation.context protected val isRotated = testSpec.config.startRotation.isRotated() protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation) protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) @@ -40,6 +45,15 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation) .launcherStrategy.supportedLauncherPackage + /** + * List of windows that are ignored when verifying that visible elements appear on 2 + * consecutive entries in the trace. + * + * b/182720234 + */ + open val ignoredWindows: List<String> = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { @@ -88,11 +102,26 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa } } + @Presubmit + @Test + open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + testSpec.assertWm { + this.visibleWindowsShownMoreThanOneConsecutiveEntry(ignoredWindows) + } + } + + @Presubmit + @Test + open fun visibleLayersShownMoreThanOneConsecutiveEntry() { + testSpec.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry(ignoredWindows) + } + } + companion object { internal const val LIVE_WALLPAPER_PACKAGE_NAME = "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2" internal const val LETTERBOX_NAME = "Letterbox" internal const val TOAST_NAME = "Toast" - internal const val SPLASH_SCREEN_NAME = "Splash Screen" } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt index d22833784bcf..8f15e5088914 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt @@ -31,8 +31,7 @@ import com.android.server.wm.flicker.layerBecomesVisible import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.appPairsDividerBecomesVisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder @@ -61,12 +60,15 @@ class OpenAppToLegacySplitScreen( } } + override val ignoredWindows: List<String> + get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, + WindowManagerStateHelper.SPLASH_SCREEN_NAME, + WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) + @FlakyTest(bugId = 178447631) @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName) - ) + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() @Presubmit @Test @@ -90,10 +92,8 @@ class OpenAppToLegacySplitScreen( @FlakyTest(bugId = 178447631) @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName) - ) + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @FlakyTest(bugId = 151179149) @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index f5174bc3cef7..33ade38d0373 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -40,8 +40,6 @@ import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry -import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.traces.layers.getVisibleBounds import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER @@ -107,8 +105,11 @@ class ResizeLegacySplitScreen( fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + testSpec.assertWm { + this.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + } @FlakyTest(bugId = 156223549) @Test @@ -144,8 +145,8 @@ class ResizeLegacySplitScreen( testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation) @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = - testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Test fun topAppLayerIsAlwaysVisible() { @@ -182,8 +183,8 @@ class ResizeLegacySplitScreen( dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - this.coversExactly(topAppBounds, "SimpleActivity") - .coversExactly(bottomAppBounds, "ImeActivity") + visibleRegion("SimpleActivity").coversExactly(topAppBounds) + visibleRegion("ImeActivity").coversExactly(bottomAppBounds) } } @@ -202,8 +203,8 @@ class ResizeLegacySplitScreen( displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - this.coversExactly(topAppBounds, sSimpleActivity) - .coversExactly(bottomAppBounds, sImeActivity) + visibleRegion(sSimpleActivity).coversExactly(topAppBounds) + visibleRegion(sImeActivity).coversExactly(bottomAppBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt index 75c33c671008..1ba1f3b3eb5d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt @@ -23,13 +23,7 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.WindowUtils import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -48,7 +42,6 @@ class EnterExitPipTest( testSpec: FlickerTestParameter ) : PipTransition(testSpec) { private val testApp = FixedAppHelper(instrumentation) - private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { @@ -84,14 +77,6 @@ class EnterExitPipTest( @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test fun showBothAppLayersThenHidePip() { testSpec.assertLayers { isVisible(testApp.defaultWindowName) @@ -105,8 +90,8 @@ class EnterExitPipTest( @Test fun testAppCoversFullScreenWithPipOnDisplay() { testSpec.assertLayersStart { - coversExactly(displayBounds, testApp.defaultWindowName) - coversAtMost(displayBounds, pipApp.defaultWindowName) + visibleRegion(testApp.defaultWindowName).coversExactly(displayBounds) + visibleRegion(pipApp.defaultWindowName).coversAtMost(displayBounds) } } @@ -114,18 +99,10 @@ class EnterExitPipTest( @Test fun pipAppCoversFullScreen() { testSpec.assertLayersEnd { - coversExactly(displayBounds, pipApp.defaultWindowName) + visibleRegion(pipApp.defaultWindowName).coversExactly(displayBounds) } } - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 2f08db1b7d0a..cd20ddee04fc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -18,20 +18,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.startRotation import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -57,15 +48,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun pipWindowBecomesVisible() { + fun pipAppWindowAlwaysVisible() { testSpec.assertWm { this.showsAppWindow(pipApp.defaultWindowName) } @@ -73,34 +56,21 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test fun pipLayerBecomesVisible() { testSpec.assertLayers { this.isVisible(pipApp.launcherName) } } - @FlakyTest(bugId = 140855415) - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @FlakyTest(bugId = 140855415) - @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - - @FlakyTest(bugId = 140855415) + @Presubmit @Test - fun noUncoveredRegions() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) + fun pipWindowBecomesVisible() { + testSpec.assertWm { + invoke("pipWindowIsNotVisible") { !it.wmState.hasPipWindow() } + .then() + .invoke("pipWindowIsVisible") { it.wmState.hasPipWindow() } + } + } companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index 9011f1a9fb6a..2beec2e8c1ed 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -18,16 +18,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.view.Surface +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT @@ -78,11 +75,19 @@ class EnterPipToOtherOrientationTest( // Enter PiP, and assert that the PiP is within bounds now that the device is back // in portrait broadcastActionTrigger.doAction(ACTION_ENTER_PIP) - wmHelper.waitPipWindowShown() + wmHelper.waitFor { it.wmState.hasPipWindow() } wmHelper.waitForAppTransitionIdle() } } + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun pipAppWindowIsAlwaysOnTop() { @@ -109,17 +114,9 @@ class EnterPipToOtherOrientationTest( @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test fun pipAppLayerHidesTestApp() { testSpec.assertLayersStart { - coversExactly(startingBounds, pipApp.defaultWindowName) + visibleRegion(pipApp.defaultWindowName).coversExactly(startingBounds) isInvisible(testApp.defaultWindowName) } } @@ -128,18 +125,10 @@ class EnterPipToOtherOrientationTest( @Test fun testAppLayerCoversFullScreen() { testSpec.assertLayersEnd { - coversExactly(endingBounds, testApp.defaultWindowName) + visibleRegion(testApp.defaultWindowName).coversExactly(endingBounds) } } - @Presubmit - @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt index 96eb66c3cc28..0037059e2c51 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt @@ -16,62 +16,14 @@ package com.android.wm.shell.flicker.pip -import android.app.WindowConfiguration import android.content.ComponentName -import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject import com.android.server.wm.traces.common.windowmanager.WindowManagerState import com.android.server.wm.traces.parser.toWindowName -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import com.google.common.truth.Truth - -inline val WindowManagerState.pinnedWindows - get() = visibleWindows - .filter { it.windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED } - -/** - * Checks if the state has any window in PIP mode - */ -fun WindowManagerState.hasPipWindow(): Boolean = pinnedWindows.isNotEmpty() /** * Checks that an activity [activity] is in PIP mode */ fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean { val windowName = activity.toWindowName() - return pinnedWindows.any { it.title == windowName } + return isInPipMode(windowName) } - -/** - * Asserts that an activity [activity] exists and is in PIP mode - */ -fun WindowManagerStateSubject.isInPipMode( - activity: ComponentName -): WindowManagerStateSubject = apply { - val windowName = activity.toWindowName() - contains(windowName) - val pinnedWindows = wmState.pinnedWindows - .map { it.title } - Truth.assertWithMessage("Window not in PIP mode") - .that(pinnedWindows) - .contains(windowName) -} - -/** - * Waits until the state has a window in PIP mode, i.e., with - * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED - */ -fun WindowManagerStateHelper.waitPipWindowShown(): Boolean = - waitFor("PIP window shown") { - val result = it.wmState.hasPipWindow() - result - } - -/** - * Waits until the state doesn't have a window in PIP mode, i.e., with - * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED - */ -fun WindowManagerStateHelper.waitPipWindowGone(): Boolean = - waitFor("PIP window gone") { - val result = !it.wmState.hasPipWindow() - result - } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt index 3e331761f767..eae7e973711c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt @@ -24,14 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import org.junit.Test import org.junit.runners.Parameterized @@ -52,22 +45,6 @@ abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransitio @Presubmit @Test - open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test open fun pipWindowBecomesInvisible() { testSpec.assertWm { this.showsAppWindow(PIP_WINDOW_TITLE) @@ -86,21 +63,6 @@ abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransitio } } - @Presubmit - @Test - open fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test - open fun noUncoveredRegions() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test - open fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - @FlakyTest(bugId = 151179149) @Test open fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt index afaf33a7c46f..c7a1c9aac86b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -51,40 +51,40 @@ class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition } } - @Postsubmit + @Presubmit @Test override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible() - @Postsubmit + @Presubmit @Test override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible() - @Postsubmit + @Presubmit @Test override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - @Postsubmit + @Presubmit @Test override fun noUncoveredRegions() = super.noUncoveredRegions() - @Postsubmit + @Presubmit @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt index 97afc65b8b61..afba508f54b7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -24,18 +24,12 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.helpers.ImeAppHelper import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.server.wm.flicker.repetitions -import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.removeAllTasksButHome +import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP import org.junit.FixMethodOrder import org.junit.Test @@ -55,7 +49,6 @@ import org.junit.runners.Parameterized class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) private val testApp = FixedAppHelper(instrumentation) - private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -85,7 +78,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t } } - @Postsubmit + @Presubmit @Test fun pipWindowInsideDisplayBounds() { testSpec.assertWm { @@ -93,7 +86,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t } } - @Postsubmit + @Presubmit @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { @@ -103,15 +96,15 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t } } - @Postsubmit + @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun pipLayerInsideDisplayBounds() { testSpec.assertLayers { @@ -119,22 +112,22 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t } } - @Postsubmit + @Presubmit @Test fun bothAppLayersVisible() { testSpec.assertLayersEnd { - coversAtMost(displayBounds, testApp.defaultWindowName) - coversAtMost(displayBounds, imeApp.defaultWindowName) + visibleRegion(testApp.defaultWindowName).coversAtMost(displayBounds) + visibleRegion(imeApp.defaultWindowName).coversAtMost(displayBounds) } } - @Postsubmit + @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible() companion object { const val TEST_REPETITIONS = 2 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt index 4c95da284d9e..5713822bba99 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt @@ -16,16 +16,15 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.google.common.truth.Truth import org.junit.FixMethodOrder import org.junit.Test @@ -57,19 +56,19 @@ class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec } } - @Postsubmit + @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Postsubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) } - @Postsubmit + @Presubmit @Test - fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) } + fun pipLayerInsideDisplay() { + testSpec.assertLayersStart { + visibleRegion(pipApp.defaultWindowName).coversAtMost(displayBounds) + } + } - @Postsubmit + @FlakyTest(bugId = 184050344) @Test fun pipWindowMovesUp() = testSpec.assertWmEnd { val initialState = this.trace?.first()?.wmState diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index df835d21e73f..852ee4726080 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -27,15 +26,11 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.startRotation -import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales +import com.android.wm.shell.flicker.helpers.FixedAppHelper import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -77,52 +72,36 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, + override fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, testSpec.config.endRotation, allStates = false) @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @FlakyTest(bugId = 140855415) - @Test - fun navBarLayerRotatesAndScales() = + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, testSpec.config.endRotation) - @FlakyTest(bugId = 140855415) + @Presubmit @Test - fun statusBarLayerRotatesScales() = + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, testSpec.config.endRotation) - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun appLayerRotates_StartingBounds() { testSpec.assertLayersStart { - coversExactly(startingBounds, fixedApp.defaultWindowName) - coversAtMost(startingBounds, pipApp.defaultWindowName) + visibleRegion(fixedApp.defaultWindowName).coversExactly(startingBounds) + visibleRegion(pipApp.defaultWindowName).coversAtMost(startingBounds) } } - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun appLayerRotates_EndingBounds() { testSpec.assertLayersEnd { - coversExactly(endingBounds, fixedApp.defaultWindowName) - coversAtMost(endingBounds, pipApp.defaultWindowName) + visibleRegion(fixedApp.defaultWindowName).coversExactly(endingBounds) + visibleRegion(pipApp.defaultWindowName).coversAtMost(endingBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt index 1bb1d2861f3f..6f17a2c57ffc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt @@ -26,14 +26,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -68,22 +61,6 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test fun appReplacesPipWindow() { testSpec.assertWm { this.showsAppWindow(PIP_WINDOW_TITLE) @@ -94,11 +71,6 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit - @Test fun appReplacesPipLayer() { testSpec.assertLayers { this.isVisible(PIP_WINDOW_TITLE) @@ -107,15 +79,13 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { } } - @Presubmit - @Test - fun noUncoveredRegions() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - - @Presubmit + @FlakyTest @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + fun testAppCoversFullScreen() { + testSpec.assertLayersStart { + visibleRegion(pipApp.defaultWindowName).coversExactly(displayBounds) + } + } @FlakyTest(bugId = 151179149) @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index b0a9afef9215..ad1ccbd10753 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -18,27 +18,37 @@ package com.android.wm.shell.flicker.pip import android.app.Instrumentation import android.content.Intent +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.isRotated import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.repetitions +import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.helpers.PipAppHelper -import com.android.wm.shell.flicker.removeAllTasksButHome import com.android.wm.shell.flicker.testapp.Components +import org.junit.Test abstract class PipTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() protected val isRotated = testSpec.config.startRotation.isRotated() protected val pipApp = PipAppHelper(instrumentation) + protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit - // Helper class to process test actions by broadcast. protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) { private fun createIntentWithAction(broadcastAction: String): Intent { @@ -121,13 +131,13 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { removeAllTasksButHome() if (!eachRun) { pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) - wmHelper.waitPipWindowShown() + wmHelper.waitFor { it.wmState.hasPipWindow() } } } eachRun { if (eachRun) { pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) - wmHelper.waitPipWindowShown() + wmHelper.waitFor { it.wmState.hasPipWindow() } } } } @@ -148,4 +158,35 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { extraSpec(this, configuration) } } + + @Presubmit + @Test + open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + + @Presubmit + @Test + open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + + @Presubmit + @Test + open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + + @Presubmit + @Test + open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + + @Presubmit + @Test + open fun navBarLayerRotatesAndScales() = + testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + + @Presubmit + @Test + open fun statusBarLayerRotatesScales() = + testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + + @Presubmit + @Test + open fun noUncoveredRegions() = + testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 7916ce59af52..9aab7f30e6f8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -25,10 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP @@ -83,11 +79,19 @@ class SetRequestedOrientationWhilePinnedTest( } } + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Presubmit @Test fun pipWindowInsideDisplay() { testSpec.assertWmStart { - coversAtMost(startingBounds, pipApp.defaultWindowName) + frameRegion(pipApp.defaultWindowName).coversAtMost(startingBounds) } } @@ -101,35 +105,25 @@ class SetRequestedOrientationWhilePinnedTest( @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - - @Presubmit - @Test fun pipLayerInsideDisplay() { testSpec.assertLayersStart { - coversAtMost(startingBounds, pipApp.defaultWindowName) + visibleRegion(pipApp.defaultWindowName).coversAtMost(startingBounds) } } @Presubmit @Test - fun pipAppLayerCoversFullScreen() { - testSpec.assertLayersEnd { - coversExactly(endingBounds, pipApp.defaultWindowName) - } + fun pipAlwaysVisible() = testSpec.assertWm { + this.showsAppWindow(pipApp.windowName) } @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun pipAppLayerCoversFullScreen() { + testSpec.assertLayersEnd { + visibleRegion(pipApp.defaultWindowName).coversExactly(endingBounds) + } + } companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java index bf84a6e30c98..da95c77d2b89 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java @@ -16,8 +16,6 @@ package com.android.wm.shell; -import android.os.Looper; - import com.android.wm.shell.common.ShellExecutor; import java.util.ArrayList; @@ -40,11 +38,6 @@ public class TestShellExecutor implements ShellExecutor { } @Override - public void removeAllCallbacks() { - mRunnables.clear(); - } - - @Override public void removeCallbacks(Runnable r) { mRunnables.remove(r); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index 4cedc483fc21..ef046d48e1cf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import android.graphics.Insets; import android.graphics.Point; import android.view.InsetsSource; import android.view.InsetsSourceControl; @@ -119,7 +120,8 @@ public class DisplayImeControllerTest { private InsetsSourceControl[] insetsSourceControl() { return new InsetsSourceControl[]{ - new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0)) + new InsetsSourceControl( + ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0), Insets.NONE) }; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index c1c4c6dd08d7..2f2bbba11646 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -205,7 +205,7 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -217,12 +217,12 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); reset(mSplitScreenStarter); mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -234,12 +234,12 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); reset(mSplitScreenStarter); mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -251,7 +251,7 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -263,7 +263,7 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); } @@ -276,13 +276,13 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); reset(mSplitScreenStarter); // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -295,13 +295,13 @@ public class DragAndDropPolicyTest { mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any()); reset(mSplitScreenStarter); // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); - verify(mSplitScreenStarter).startIntent(any(), any(), any(), + verify(mSplitScreenStarter).startIntent(any(), any(), eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any()); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java index d6bcf0375f32..3f47c040dd8d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java @@ -22,6 +22,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; @@ -35,6 +36,7 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import org.junit.Before; import org.junit.Test; @@ -48,7 +50,8 @@ import org.mockito.MockitoAnnotations; public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase { private DisplayAreaInfo mDisplayAreaInfo; private Display mDisplay; - private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer; + private DisplayLayout mDisplayLayout; + private OneHandedBackgroundPanelOrganizer mSpiedBackgroundPanelOrganizer; private WindowContainerToken mToken; private SurfaceControl mLeash; private TestableLooper mTestableLooper; @@ -65,37 +68,38 @@ public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase { mToken = new WindowContainerToken(mMockRealToken); mLeash = new SurfaceControl(); mDisplay = mContext.getDisplay(); + mDisplayLayout = new DisplayLayout(mContext, mDisplay); when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, FEATURE_ONE_HANDED_BACKGROUND_PANEL); - mBackgroundPanelOrganizer = new OneHandedBackgroundPanelOrganizer(mContext, mWindowManager, - mMockDisplayController, Runnable::run); + mSpiedBackgroundPanelOrganizer = spy( + new OneHandedBackgroundPanelOrganizer(mContext, mDisplayLayout, Runnable::run)); } @Test public void testOnDisplayAreaAppeared() { - mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); + mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); mTestableLooper.processAllMessages(); - assertThat(mBackgroundPanelOrganizer.getBackgroundSurface()).isNotNull(); + assertThat(mSpiedBackgroundPanelOrganizer.getBackgroundSurface()).isNotNull(); } @Test public void testShowBackgroundLayer() { - mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); - mBackgroundPanelOrganizer.showBackgroundPanelLayer(); + mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); + mSpiedBackgroundPanelOrganizer.showBackgroundPanelLayer(); mTestableLooper.processAllMessages(); - assertThat(mBackgroundPanelOrganizer.mIsShowing).isTrue(); + assertThat(mSpiedBackgroundPanelOrganizer.mIsShowing).isTrue(); } @Test public void testRemoveBackgroundLayer() { - mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); - mBackgroundPanelOrganizer.removeBackgroundPanelLayer(); + mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash); + mSpiedBackgroundPanelOrganizer.removeBackgroundPanelLayer(); mTestableLooper.processAllMessages(); - assertThat(mBackgroundPanelOrganizer.mIsShowing).isFalse(); + assertThat(mSpiedBackgroundPanelOrganizer.mIsShowing).isFalse(); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index c5221dee9216..e309f9659338 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -16,28 +16,32 @@ package com.android.wm.shell.onehanded; -import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.om.IOverlayManager; +import android.graphics.Rect; import android.os.Handler; +import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.util.ArrayMap; import android.view.Display; +import android.view.Surface; import android.view.SurfaceControl; import androidx.test.filters.SmallTest; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -50,7 +54,10 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class OneHandedControllerTest extends OneHandedTestCase { + private int mCurrentUser = UserHandle.myUserId(); + Display mDisplay; + DisplayLayout mDisplayLayout; OneHandedController mSpiedOneHandedController; OneHandedTimeoutHandler mSpiedTimeoutHandler; @@ -67,6 +74,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock OneHandedGestureHandler mMockGestureHandler; @Mock + OneHandedSettingsUtil mMockSettingsUitl; + @Mock OneHandedUiEventLogger mMockUiEventLogger; @Mock IOverlayManager mMockOverlayManager; @@ -79,34 +88,43 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock Handler mMockShellMainHandler; - final boolean mDefaultEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - getTestContext().getContentResolver()); - final boolean mDefaultSwipeToNotificationEnabled = - OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - getTestContext().getContentResolver()); - final boolean mDefaultTapAppToExitEnabled = OneHandedSettingsUtil.getSettingsTapsAppToExit( - getTestContext().getContentResolver()); + final boolean mDefaultEnabled = true; + final boolean mDefaultSwipeToNotificationEnabled = false; + final boolean mDefaultTapAppToExitEnabled = true; @Before public void setUp() { MockitoAnnotations.initMocks(this); mDisplay = mContext.getDisplay(); + mDisplayLayout = new DisplayLayout(mContext, mDisplay); mSpiedTimeoutHandler = spy(new OneHandedTimeoutHandler(mMockShellMainExecutor)); when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); when(mMockBackgroundOrganizer.getBackgroundSurface()).thenReturn(mMockLeash); + when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn( + mDefaultEnabled); + when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn( + OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS); + when(mMockSettingsUitl.getSettingsTapsAppToExit(any(), anyInt())).thenReturn( + mDefaultTapAppToExitEnabled); + when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( + mDefaultSwipeToNotificationEnabled); + + when(mMockDisplayAreaOrganizer.getLastDisplayBounds()).thenReturn( + new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height())); + when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mDisplayLayout); mSpiedOneHandedController = spy(new OneHandedController( mContext, - mWindowManager, mMockDisplayController, mMockBackgroundOrganizer, mMockDisplayAreaOrganizer, mMockTouchHandler, mMockTutorialHandler, mMockGestureHandler, + mMockSettingsUitl, mSpiedTimeoutHandler, mMockUiEventLogger, mMockOverlayManager, @@ -121,22 +139,13 @@ public class OneHandedControllerTest extends OneHandedTestCase { final OneHandedAnimationController animationController = new OneHandedAnimationController( mContext); OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer( - mContext, mWindowManager, mMockDisplayController, animationController, - mMockTutorialHandler, mMockBackgroundOrganizer, mMockShellMainExecutor); + mContext, mDisplayLayout, animationController, mMockTutorialHandler, + mMockBackgroundOrganizer, mMockShellMainExecutor); assertThat(displayAreaOrganizer.isInOneHanded()).isFalse(); } @Test - public void testNoRegisterAndUnregisterInSameCall() { - if (mDefaultEnabled) { - verify(mMockDisplayAreaOrganizer, never()).unregisterOrganizer(); - } else { - verify(mMockDisplayAreaOrganizer, never()).registerOrganizer(FEATURE_ONE_HANDED); - } - } - - @Test public void testStartOneHandedShouldTriggerScheduleOffset() { when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false); mSpiedOneHandedController.setOneHandedEnabled(true); @@ -190,35 +199,39 @@ public class OneHandedControllerTest extends OneHandedTestCase { public void testUpdateEnabled() { mSpiedOneHandedController.setOneHandedEnabled(true); - verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(mDefaultEnabled); - verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled( - mDefaultEnabled || mDefaultSwipeToNotificationEnabled); + verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean()); + verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean()); } @Test public void testUpdateSwipeToNotification() { mSpiedOneHandedController.setSwipeToNotificationEnabled(mDefaultSwipeToNotificationEnabled); - verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(mDefaultEnabled); - verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled( - mDefaultEnabled || mDefaultSwipeToNotificationEnabled); + verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean()); + verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean()); } @Test - public void testSettingsObserverUpdateTapAppToExit() { - mSpiedOneHandedController.onTaskChangeExitSettingChanged(); - if (mDefaultTapAppToExitEnabled) { - verify(mMockTaskStackListener, atLeastOnce()).addListener(any()); - } else { - verify(mMockTaskStackListener, atLeastOnce()).removeListener(any()); - } + public void testTapAppToExitEnabledAddListener() { + mSpiedOneHandedController.setTaskChangeToExit(mDefaultTapAppToExitEnabled); + + // If device settings default ON, then addListener() will be trigger 1 time at init + verify(mMockTaskStackListener, atLeastOnce()).addListener(any()); + } + + @Test + public void testTapAppToExitDisabledRemoveListener() { + mSpiedOneHandedController.setTaskChangeToExit(!mDefaultTapAppToExitEnabled); + + // If device settings default ON, then removeListener() will be trigger 1 time at init + verify(mMockTaskStackListener, atLeastOnce()).removeListener(any()); } @Test public void testSettingsObserverUpdateEnabled() { mSpiedOneHandedController.onEnabledSettingChanged(); - verify(mSpiedOneHandedController).setOneHandedEnabled(mDefaultEnabled); + verify(mSpiedOneHandedController).setOneHandedEnabled(anyBoolean()); } @Test @@ -232,14 +245,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { public void testSettingsObserverUpdateSwipeToNotification() { mSpiedOneHandedController.onSwipeToNotificationEnabledSettingChanged(); - // Swipe to notification function is opposite with one handed mode function - if (mDefaultSwipeToNotificationEnabled) { - verify(mSpiedOneHandedController).setSwipeToNotificationEnabled( - mDefaultSwipeToNotificationEnabled); - } else { - verify(mSpiedOneHandedController, never()).setSwipeToNotificationEnabled( - mDefaultSwipeToNotificationEnabled); - } + verify(mSpiedOneHandedController).setSwipeToNotificationEnabled(anyBoolean()); } @Test @@ -288,4 +294,67 @@ public class OneHandedControllerTest extends OneHandedTestCase { verify(mMockDisplayAreaOrganizer).scheduleOffset(anyInt(), anyInt()); } + + @Test + public void testRotation90CanNotStartOneHanded() { + final DisplayLayout landscapeDisplayLayout = new DisplayLayout(mDisplayLayout); + landscapeDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false); + when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(landscapeDisplayLayout); + mSpiedOneHandedController.setOneHandedEnabled(true); + mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */); + mSpiedOneHandedController.startOneHanded(); + + verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt()); + } + + @Test + public void testRotation180CanStartOneHanded() { + final DisplayLayout testDisplayLayout = new DisplayLayout(mDisplayLayout); + testDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); + when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false); + when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(testDisplayLayout); + mSpiedOneHandedController.setOneHandedEnabled(true); + mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */); + mSpiedOneHandedController.startOneHanded(); + + verify(mMockDisplayAreaOrganizer).scheduleOffset(anyInt(), anyInt()); + } + + @Test + public void testRotation270CanNotStartOneHanded() { + final DisplayLayout testDisplayLayout = new DisplayLayout(mDisplayLayout); + testDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270); + when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false); + when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(testDisplayLayout); + mSpiedOneHandedController.setOneHandedEnabled(true); + mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */); + mSpiedOneHandedController.startOneHanded(); + + verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt()); + } + + @Test + public void testDisabled3ButtonGestureWhenKeyguardOn() { + final boolean isOneHandedEnabled = true; + final boolean isLockWhenKeyguardOn = true; + final boolean isEnabledWhenKeyguardOn = false; + mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled); + mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn); + + verify(mMockGestureHandler).onGestureEnabled(isEnabledWhenKeyguardOn); + } + + @Test + public void testEnabled3ButtonGestureWhenKeyguardGoingAway() { + final boolean isOneHandedEnabled = true; + final boolean isLockWhenKeyguardOn = false; + final boolean isEnabledWhenKeyguardOn = false; + mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled); + reset(mMockGestureHandler); + + mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn); + + verify(mMockGestureHandler).onGestureEnabled(isOneHandedEnabled); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index 1fa1e2ff69b6..f654bb59ef0c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -19,6 +19,9 @@ package com.android.wm.shell.onehanded; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED; +import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT; +import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyFloat; @@ -32,6 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Binder; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -47,6 +51,7 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import org.junit.Before; @@ -67,6 +72,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { DisplayAreaInfo mDisplayAreaInfo; Display mDisplay; + DisplayLayout mDisplayLayout; OneHandedDisplayAreaOrganizer mSpiedDisplayAreaOrganizer; OneHandedTutorialHandler mTutorialHandler; OneHandedAnimationController.OneHandedTransitionAnimator mFakeAnimator; @@ -103,6 +109,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mToken = new WindowContainerToken(mMockRealToken); mLeash = new SurfaceControl(); mDisplay = mContext.getDisplay(); + mDisplayLayout = new DisplayLayout(mContext, mDisplay); mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, FEATURE_ONE_HANDED); mDisplayAreaInfo.configuration.orientation = Configuration.ORIENTATION_PORTRAIT; when(mMockAnimationController.getAnimator(any(), any(), any(), any())).thenReturn(null); @@ -121,8 +128,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { when(mMockLeash.getHeight()).thenReturn(DISPLAY_HEIGHT); mSpiedDisplayAreaOrganizer = spy(new OneHandedDisplayAreaOrganizer(mContext, - mWindowManager, - mMockDisplayController, + mDisplayLayout, mMockAnimationController, mTutorialHandler, mMockBackgroundOrganizer, @@ -173,8 +179,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_portrait_0_to_landscape_90() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 0 -> 90 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90, + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90, mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @@ -182,8 +190,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_portrait_0_to_seascape_270() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 0 -> 270 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_270, + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270, mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @@ -191,8 +201,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_portrait_180_to_landscape_90() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 180 -> 90 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_90, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90, mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @@ -200,8 +214,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_portrait_180_to_seascape_270() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 180 -> 270 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_270, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270, mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @@ -209,8 +227,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_landscape_90_to_portrait_0() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 90 -> 0 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_0, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0, mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @@ -218,26 +240,38 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_landscape_90_to_portrait_180() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 90 -> 180 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_180, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180, mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @Test - public void testRotation_Seascape_270_to_portrait_0() { + public void testRotation_seascape_270_to_portrait_0() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 270 -> 0 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_0, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0, mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @Test - public void testRotation_seascape_90_to_portrait_180() { + public void testRotation_seascape_270_to_portrait_180() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 270 -> 180 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_180, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180, mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @@ -245,7 +279,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_portrait_0_to_portrait_0() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 0 -> 0 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_0, + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0, + mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset( mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt()); } @@ -254,16 +291,23 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_portrait_0_to_portrait_180() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 0 -> 180 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_180, + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180, mMockWindowContainerTransaction); - verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt()); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); + verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @Test public void testRotation_portrait_180_to_portrait_180() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 180 -> 180 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_180, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180, + mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset( mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt()); } @@ -272,16 +316,25 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_portrait_180_to_portrait_0() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 180 -> 0 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_0, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0, mMockWindowContainerTransaction); - verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt()); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); + verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @Test public void testRotation_landscape_90_to_landscape_90() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 90 -> 90 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_90, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90, + mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset( mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt()); } @@ -290,26 +343,60 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { public void testRotation_landscape_90_to_seascape_270() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 90 -> 270 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_270, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270, mMockWindowContainerTransaction); - verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt()); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction); + verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); } @Test public void testRotation_seascape_270_to_seascape_270() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 270 -> 270 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_270, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270, + mMockWindowContainerTransaction); + + verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset( mMockWindowContainerTransaction); verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt()); } @Test - public void testRotation_seascape_90_to_landscape_90() { + public void testRotation_seascape_270_to_landscape_90() { when(mMockLeash.isValid()).thenReturn(false); // Rotate 270 -> 90 - mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_90, + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270); + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90, mMockWindowContainerTransaction); - verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt()); + + verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset( + mMockWindowContainerTransaction); + verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt()); + } + + @Test + public void testTriggerOffset() { + final Rect testBounds = mSpiedDisplayAreaOrganizer.getLastDisplayBounds(); + final int offset = 100; + testBounds.offsetTo(0, offset); + mSpiedDisplayAreaOrganizer.finishOffset(offset, TRANSITION_DIRECTION_TRIGGER); + + assertThat(mSpiedDisplayAreaOrganizer.getLastDisplayBounds()).isEqualTo(testBounds); + } + + @Test + public void testExitOffsetToZero() { + final Rect testBounds = mSpiedDisplayAreaOrganizer.getLastDisplayBounds(); + final int offset = 100; + mSpiedDisplayAreaOrganizer.finishOffset(offset, TRANSITION_DIRECTION_TRIGGER); + mSpiedDisplayAreaOrganizer.finishOffset(0, TRANSITION_DIRECTION_EXIT); + + assertThat(mSpiedDisplayAreaOrganizer.getLastDisplayBounds()).isEqualTo(testBounds); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java index f683e4af41bd..5d82a700545c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java @@ -16,21 +16,19 @@ package com.android.wm.shell.onehanded; -import static android.view.Display.DEFAULT_DISPLAY; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; import android.view.Surface; import android.view.ViewConfiguration; -import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; -import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; @@ -44,24 +42,20 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class OneHandedGestureHandlerTest extends OneHandedTestCase { OneHandedGestureHandler mGestureHandler; - @Mock - DisplayController mMockDisplayController; + DisplayLayout mDisplayLayout; @Mock DisplayLayout mMockDisplayLayout; @Mock ShellExecutor mMockShellMainExecutor; - @Mock - WindowContainerTransaction mMockWct; @Before public void setUp() { final int mockNavBarHeight = 100; MockitoAnnotations.initMocks(this); - mGestureHandler = new OneHandedGestureHandler(mContext, mWindowManager, - mMockDisplayController, ViewConfiguration.get(mTestContext), - mMockShellMainExecutor); + mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay()); + mGestureHandler = new OneHandedGestureHandler(mContext, mDisplayLayout, + ViewConfiguration.get(mTestContext), mMockShellMainExecutor); when(mMockDisplayLayout.navBarFrameHeight()).thenReturn(mockNavBarHeight); - when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(mMockDisplayLayout); } @Test @@ -81,7 +75,7 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase { @Test public void testOneHandedDisabled_shouldDisposeInputChannel() { - mGestureHandler.onOneHandedEnabled(false); + mGestureHandler.onGestureEnabled(false); assertThat(mGestureHandler.mInputMonitor).isNull(); assertThat(mGestureHandler.mInputEventReceiver).isNull(); @@ -89,7 +83,7 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase { @Test public void testChangeNavBarToNon3Button_shouldDisposeInputChannel() { - mGestureHandler.onOneHandedEnabled(true); + mGestureHandler.onGestureEnabled(true); mGestureHandler.onThreeButtonModeEnabled(false); assertThat(mGestureHandler.mInputMonitor).isNull(); @@ -98,11 +92,38 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase { @Test public void testOnlyHandleGestureInPortraitMode() { - mGestureHandler.onOneHandedEnabled(true); - mGestureHandler.onRotateDisplay(DEFAULT_DISPLAY, Surface.ROTATION_0, Surface.ROTATION_90, - mMockWct); + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + mGestureHandler.onGestureEnabled(true); + mGestureHandler.onRotateDisplay(mDisplayLayout); assertThat(mGestureHandler.mInputMonitor).isNull(); assertThat(mGestureHandler.mInputEventReceiver).isNull(); } + + @Test + public void testRotation90ShouldNotRegisterEventReceiver() throws InterruptedException { + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + mGestureHandler.onGestureEnabled(true); + mGestureHandler.onRotateDisplay(mDisplayLayout); + + verify(mMockShellMainExecutor, never()).executeBlocking(any()); + } + + @Test + public void testRotation180ShouldNotRegisterEventReceiver() throws InterruptedException { + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); + mGestureHandler.onGestureEnabled(true); + mGestureHandler.onRotateDisplay(mDisplayLayout); + + verify(mMockShellMainExecutor, never()).executeBlocking(any()); + } + + @Test + public void testRotation270ShouldNotRegisterEventReceiver() throws InterruptedException { + mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270); + mGestureHandler.onGestureEnabled(true); + mGestureHandler.onRotateDisplay(mDisplayLayout); + + verify(mMockShellMainExecutor, never()).executeBlocking(any()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java index 61643d86c8d9..1e6c41af4397 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java @@ -16,17 +16,11 @@ package com.android.wm.shell.onehanded; -import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_LONG_IN_SECONDS; -import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS; -import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER; -import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS; - -import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; import android.content.ContentResolver; import android.database.ContentObserver; -import android.net.Uri; -import android.provider.Settings; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -34,76 +28,30 @@ import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class OneHandedSettingsUtilTest extends OneHandedTestCase { - ContentResolver mContentResolver; - ContentObserver mContentObserver; - boolean mOnChanged; + OneHandedSettingsUtil mSettingsUtil; + + @Mock + ContentResolver mMockContentResolver; + @Mock + ContentObserver mMockContentObserver; @Before public void setUp() { - mContentResolver = mContext.getContentResolver(); - mContentObserver = new ContentObserver(mContext.getMainThreadHandler()) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - mOnChanged = true; - } - }; - } - - @Test - public void testRegisterSecureKeyObserver() { - final Uri result = OneHandedSettingsUtil.registerSettingsKeyObserver( - Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver); + MockitoAnnotations.initMocks(this); - assertThat(result).isNotNull(); - - OneHandedSettingsUtil.registerSettingsKeyObserver( - Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver); + mSettingsUtil = new OneHandedSettingsUtil(); } @Test public void testUnregisterSecureKeyObserver() { - OneHandedSettingsUtil.registerSettingsKeyObserver( - Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver); - OneHandedSettingsUtil.unregisterSettingsKeyObserver(mContentResolver, mContentObserver); - - assertThat(mOnChanged).isFalse(); - - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.TAPS_APP_TO_EXIT, 0); - - assertThat(mOnChanged).isFalse(); - } + mSettingsUtil.unregisterSettingsKeyObserver(mMockContentResolver, mMockContentObserver); - @Test - public void testGetSettingsIsOneHandedModeEnabled() { - assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeEnabled( - mContentResolver)).isAnyOf(true, false); - } - - @Test - public void testGetSettingsTapsAppToExit() { - assertThat(OneHandedSettingsUtil.getSettingsTapsAppToExit( - mContentResolver)).isAnyOf(true, false); - } - - @Test - public void testGetSettingsOneHandedModeTimeout() { - assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeTimeout( - mContentResolver)).isAnyOf( - ONE_HANDED_TIMEOUT_NEVER, - ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS, - ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS, - ONE_HANDED_TIMEOUT_LONG_IN_SECONDS); - } - - @Test - public void testGetSettingsSwipeToNotificationEnabled() { - assertThat(OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled( - mContentResolver)).isAnyOf(true, false); + verify(mMockContentResolver).unregisterContentObserver(any()); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java index 69c537c2efbe..2886bb1e905a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java @@ -64,6 +64,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { Handler mMockShellMainHandler; @Mock OneHandedUiEventLogger mMockUiEventLogger; + @Mock + OneHandedSettingsUtil mMockSettingsUtil; @Before public void setUp() { @@ -73,13 +75,13 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); mOneHandedController = new OneHandedController( mContext, - mWindowManager, mMockDisplayController, mMockBackgroundOrganizer, mMockDisplayAreaOrganizer, mMockTouchHandler, mMockTutorialHandler, mMockGestureHandler, + mMockSettingsUtil, mTimeoutHandler, mMockUiEventLogger, mMockOverlayManager, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index f2b4e9761226..700bf7850604 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -42,6 +42,7 @@ import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; @@ -54,6 +55,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** * Unit tests for {@link PipController} */ @@ -75,6 +78,7 @@ public class PipControllerTest extends ShellTestCase { @Mock private PipBoundsState mMockPipBoundsState; @Mock private TaskStackListenerImpl mMockTaskStackListener; @Mock private ShellExecutor mMockExecutor; + @Mock private Optional<OneHandedController> mMockOneHandedController; @Before public void setUp() throws RemoteException { @@ -83,7 +87,7 @@ public class PipControllerTest extends ShellTestCase { mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, - mMockTaskStackListener, mMockExecutor); + mMockTaskStackListener, mMockOneHandedController, mMockExecutor); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; @@ -116,7 +120,7 @@ public class PipControllerTest extends ShellTestCase { mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, - mMockTaskStackListener, mMockExecutor)); + mMockTaskStackListener, mMockOneHandedController, mMockExecutor)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index 207db9e80511..78b3d4e3ca8a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -83,12 +83,11 @@ public class StartingSurfaceDrawerTests { } @Override - protected boolean postAddWindow(int taskId, IBinder appToken, + protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm, WindowManager.LayoutParams params) { // listen for addView mAddWindowForTask = taskId; mViewThemeResId = view.getContext().getThemeResId(); - return true; } @Override @@ -113,7 +112,8 @@ public class StartingSurfaceDrawerTests { spyOn(context); spyOn(realWindowManager); try { - doReturn(context).when(context).createPackageContext(anyString(), anyInt()); + doReturn(context).when(context) + .createPackageContextAsUser(anyString(), anyInt(), any()); } catch (PackageManager.NameNotFoundException e) { // } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 926108c41e5e..c1733de75535 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -30,6 +31,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -295,6 +298,44 @@ public class ShellTransitionTests { verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any()); } + @Test + public void testOneShotRemoteHandler() { + Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, + mMainExecutor, mAnimExecutor); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + final boolean[] remoteCalled = new boolean[]{false}; + final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction(); + IRemoteTransition testRemote = new IRemoteTransition.Stub() { + @Override + public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + remoteCalled[0] = true; + finishCallback.onTransitionFinished(remoteFinishWCT); + } + }; + + final int transitType = TRANSIT_FIRST_CUSTOM + 1; + + OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor, testRemote); + // Verify that it responds to the remote but not other things. + IBinder transitToken = new Binder(); + assertNotNull(oneShot.handleRequest(transitToken, + new TransitionRequestInfo(transitType, null, testRemote))); + assertNull(oneShot.handleRequest(transitToken, + new TransitionRequestInfo(transitType, null, null))); + + Transitions.TransitionFinishCallback testFinish = + mock(Transitions.TransitionFinishCallback.class); + // Verify that it responds to animation properly + oneShot.setTransition(transitToken); + IBinder anotherToken = new Binder(); + assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0), + mock(SurfaceControl.Transaction.class), testFinish)); + assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0), + mock(SurfaceControl.Transaction.class), testFinish)); + } + class TransitionInfoBuilder { final TransitionInfo mInfo; diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index c0ef7be8b673..7e45f952d389 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -226,8 +226,6 @@ void AssetManager2::BuildDynamicRefTable() { } void AssetManager2::DumpToLog() const { - base::ScopedLogSeverity _log(base::INFO); - LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this); std::string list; @@ -1721,7 +1719,6 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& o) { } void Theme::Dump() const { - base::ScopedLogSeverity _log(base::INFO); LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_); for (int p = 0; p < packages_.size(); p++) { diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 168a863df2bc..1e90b7c71376 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1379,6 +1379,11 @@ struct ResTable_typeSpec enum : uint32_t { // Additional flag indicating an entry is public. SPEC_PUBLIC = 0x40000000u, + + // Additional flag indicating the resource id for this resource may change in a future + // build. If this flag is set, the SPEC_PUBLIC flag is also set since the resource must be + // public to be exposed as an API to other applications. + SPEC_STAGED_API = 0x20000000u, }; }; diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 971a53a8b2dc..e58f31fd15eb 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -126,7 +126,7 @@ bool Properties::load() { SkAndroidFrameworkTraceUtil::setEnableTracing( base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, false)); - runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false); + runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index dcb79babad24..ea9cbd592d29 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -160,7 +160,13 @@ enum DebugLevel { /** * Property for whether this is running in the emulator. */ -#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu" +#define PROPERTY_IS_EMULATOR "ro.boot.qemu" + +/** + * Turns on the Skia GPU option "reduceOpsTaskSplitting" which improves GPU + * efficiency but may increase VRAM consumption. Default is "false". + */ +#define PROPERTY_REDUCE_OPS_TASK_SPLITTING "renderthread.skia.reduceopstasksplitting" /////////////////////////////////////////////////////////////////////////////// // Misc diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index b71bb07dbc86..145526996678 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -120,12 +120,6 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTran int imgHeight = image->height(); sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext()); - if (bitmap->colorType() == kRGBA_F16_SkColorType && - !grContext->colorTypeSupportedAsSurface(bitmap->colorType())) { - ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported"); - return CopyResult::DestinationInvalid; - } - CopyResult copyResult = CopyResult::UnknownError; int displayedWidth = imgWidth, displayedHeight = imgHeight; @@ -159,12 +153,10 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTran bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect, SkBitmap* bitmap) { - /* This intermediate surface is present to work around a bug in SwiftShader that - * prevents us from reading the contents of the layer's texture directly. The - * workaround involves first rendering that texture into an intermediate buffer and - * then reading from the intermediate buffer into the bitmap. - * Another reason to render in an offscreen buffer is to scale and to avoid an issue b/62262733 - * with reading incorrect data from EGLImage backed SkImage (likely a driver bug). + /* This intermediate surface is present to work around limitations that LayerDrawable expects + * to render into a GPU backed canvas. Additionally, the offscreen buffer solution works around + * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a + * software buffer. */ sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, bitmap->info(), 0, diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp index d4fd1053b17f..0804e0aec278 100644 --- a/libs/hwui/effects/StretchEffect.cpp +++ b/libs/hwui/effects/StretchEffect.cpp @@ -33,7 +33,8 @@ static const SkString stretchShader = SkString(R"( uniform float uMaxStretchIntensity; // Maximum percentage to stretch beyond bounds of target - uniform float uStretchAffectedDist; + uniform float uStretchAffectedDistX; + uniform float uStretchAffectedDistY; // Distance stretched as a function of the normalized overscroll times // scale intensity @@ -57,8 +58,7 @@ static const SkString stretchShader = SkString(R"( uniform float viewportWidth; // target height in pixels uniform float viewportHeight; // target width in pixels - void computeOverscrollStart( - out float outPos, + float computeOverscrollStart( float inPos, float overscroll, float uStretchAffectedDist, @@ -67,11 +67,10 @@ static const SkString stretchShader = SkString(R"( float offsetPos = uStretchAffectedDist - inPos; float posBasedVariation = smoothstep(0., uStretchAffectedDist, offsetPos); float stretchIntensity = overscroll * posBasedVariation; - outPos = distanceStretched - (offsetPos / (1. + stretchIntensity)); + return distanceStretched - (offsetPos / (1. + stretchIntensity)); } - void computeOverscrollEnd( - out float outPos, + float computeOverscrollEnd( float inPos, float overscroll, float reverseStretchDist, @@ -81,21 +80,23 @@ static const SkString stretchShader = SkString(R"( float offsetPos = inPos - reverseStretchDist; float posBasedVariation = (smoothstep(0., uStretchAffectedDist, offsetPos)); float stretchIntensity = (-overscroll) * posBasedVariation; - outPos = 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity))); + return 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity))); } - void computeOverscroll( - out float outPos, + // Prefer usage of return values over out parameters as it enables + // SKSL to properly inline method calls and works around potential GPU + // driver issues on Wembly. See b/182566543 for details + float computeOverscroll( float inPos, float overscroll, float uStretchAffectedDist, float distanceStretched, float distanceDiff ) { - if (overscroll > 0) { + float outPos = inPos; + if (overscroll > 0) { if (inPos <= uStretchAffectedDist) { - computeOverscrollStart( - outPos, + outPos = computeOverscrollStart( inPos, overscroll, uStretchAffectedDist, @@ -108,8 +109,7 @@ static const SkString stretchShader = SkString(R"( if (overscroll < 0) { float stretchAffectedDist = 1. - uStretchAffectedDist; if (inPos >= stretchAffectedDist) { - computeOverscrollEnd( - outPos, + outPos = computeOverscrollEnd( inPos, overscroll, stretchAffectedDist, @@ -120,6 +120,7 @@ static const SkString stretchShader = SkString(R"( outPos = -distanceDiff + inPos; } } + return outPos; } vec4 main(vec2 coord) { @@ -134,19 +135,17 @@ static const SkString stretchShader = SkString(R"( inV += uScrollY; outU = inU; outV = inV; - computeOverscroll( - outU, + outU = computeOverscroll( inU, uOverscrollX, - uStretchAffectedDist, + uStretchAffectedDistX, uDistanceStretchedX, uDistDiffX ); - computeOverscroll( - outV, + outV = computeOverscroll( inV, uOverscrollY, - uStretchAffectedDist, + uStretchAffectedDistY, uDistanceStretchedY, uDistDiffY ); @@ -166,16 +165,14 @@ sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapsho return mStretchFilter; } - float distanceNotStretchedX = maxStretchAmount / stretchArea.width(); - float distanceNotStretchedY = maxStretchAmount / stretchArea.height(); - float normOverScrollDistX = mStretchDirection.x(); - float normOverScrollDistY = mStretchDirection.y(); - float distanceStretchedX = maxStretchAmount / (1 + abs(normOverScrollDistX)); - float distanceStretchedY = maxStretchAmount / (1 + abs(normOverScrollDistY)); - float diffX = distanceStretchedX - distanceNotStretchedX; - float diffY = distanceStretchedY - distanceNotStretchedY; float viewportWidth = stretchArea.width(); float viewportHeight = stretchArea.height(); + float normOverScrollDistX = mStretchDirection.x(); + float normOverScrollDistY = mStretchDirection.y(); + float distanceStretchedX = maxStretchAmountX / (1 + abs(normOverScrollDistX)); + float distanceStretchedY = maxStretchAmountY / (1 + abs(normOverScrollDistY)); + float diffX = distanceStretchedX; + float diffY = distanceStretchedY; if (mBuilder == nullptr) { mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect()); @@ -183,7 +180,8 @@ sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapsho mBuilder->child("uContentTexture") = snapshotImage->makeShader( SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear)); - mBuilder->uniform("uStretchAffectedDist").set(&maxStretchAmount, 1); + mBuilder->uniform("uStretchAffectedDistX").set(&maxStretchAmountX, 1); + mBuilder->uniform("uStretchAffectedDistY").set(&maxStretchAmountY, 1); mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1); mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1); mBuilder->uniform("uDistDiffX").set(&diffX, 1); diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h index d2da06b31f68..8221b41ff4e5 100644 --- a/libs/hwui/effects/StretchEffect.h +++ b/libs/hwui/effects/StretchEffect.h @@ -33,8 +33,12 @@ public: SmoothStep, }; - StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmount) - : stretchArea(area), maxStretchAmount(maxStretchAmount), mStretchDirection(direction) {} + StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmountX, + float maxStretchAmountY) + : stretchArea(area) + , maxStretchAmountX(maxStretchAmountX) + , maxStretchAmountY(maxStretchAmountY) + , mStretchDirection(direction) {} StretchEffect() {} @@ -50,7 +54,8 @@ public: this->stretchArea = other.stretchArea; this->mStretchDirection = other.mStretchDirection; this->mStretchFilter = nullptr; - this->maxStretchAmount = other.maxStretchAmount; + this->maxStretchAmountX = other.maxStretchAmountX; + this->maxStretchAmountY = other.maxStretchAmountY; return *this; } @@ -67,13 +72,15 @@ public: return setEmpty(); } stretchArea.join(other.stretchArea); - maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount); + maxStretchAmountX = std::max(maxStretchAmountX, other.maxStretchAmountX); + maxStretchAmountY = std::max(maxStretchAmountY, other.maxStretchAmountY); } sk_sp<SkImageFilter> getImageFilter(const sk_sp<SkImage>& snapshotImage) const; SkRect stretchArea {0, 0, 0, 0}; - float maxStretchAmount = 0; + float maxStretchAmountX = 0; + float maxStretchAmountY = 0; void setStretchDirection(const SkVector& direction) { mStretchFilter = nullptr; diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index ade63e5b832c..5d9fad5b676e 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -45,7 +45,8 @@ sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { return SkColorSpace::MakeSRGB(); } -ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker) +ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker, + SkCodec::ZeroInitialized zeroInit) : mCodec(std::move(codec)) , mPeeker(std::move(peeker)) , mDecodeSize(mCodec->codec()->dimensions()) @@ -57,6 +58,7 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() } : mDecodeSize; this->rewind(); + mOptions.fZeroInitialized = zeroInit; } ImageDecoder::~ImageDecoder() = default; @@ -446,10 +448,17 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { ALOGE("Failed to invert matrix!"); } } + + // Even if the client did not provide zero initialized memory, the + // memory we decode into is. + mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized; } auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions); + // The next call to decode() may not provide zero initialized memory. + mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized; + if (scale || handleOrigin || mCropRect) { SkBitmap scaledBm; if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) { diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index cbfffd5e9291..cef2233fc371 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -34,8 +34,8 @@ public: std::unique_ptr<SkAndroidCodec> mCodec; sk_sp<SkPngChunkReader> mPeeker; - ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, - sk_sp<SkPngChunkReader> peeker = nullptr); + ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker = nullptr, + SkCodec::ZeroInitialized zeroInit = SkCodec::kNo_ZeroInitialized); ~ImageDecoder(); SkISize getSampledDimensions(int sampleSize) const; diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index ad7741b61e9f..f7b8c014be6e 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -141,7 +141,8 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream, } const bool isNinePatch = peeker->mPatch != nullptr; - ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker)); + ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker), + SkCodec::kYes_ZeroInitialized); return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID, reinterpret_cast<jlong>(decoder), decoder->width(), decoder->height(), animated, isNinePatch); diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index fc7d0d181949..fffa80614370 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -181,9 +181,10 @@ static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA j static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jfloat left, jfloat top, jfloat right, - jfloat bottom, jfloat vX, jfloat vY, jfloat max) { - StretchEffect effect = - StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), {.fX = vX, .fY = vY}, max); + jfloat bottom, jfloat vX, jfloat vY, jfloat maxX, + jfloat maxY) { + StretchEffect effect = StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), + {.fX = vX, .fY = vY}, maxX, maxY); RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith( effect); @@ -662,7 +663,7 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod, info.canvasContext.getFrameNumber(), area.left, area.top, area.right, area.bottom, stretchDirection.fX, stretchDirection.fY, - effect->maxStretchAmount); + effect->maxStretchAmountX, effect->maxStretchAmountY); #endif env->DeleteLocalRef(localref); } @@ -738,7 +739,7 @@ static const JNINativeMethod gMethods[] = { {"nSetOutlineEmpty", "(J)Z", (void*)android_view_RenderNode_setOutlineEmpty}, {"nSetOutlineNone", "(J)Z", (void*)android_view_RenderNode_setOutlineNone}, {"nClearStretch", "(J)Z", (void*)android_view_RenderNode_clearStretch}, - {"nStretch", "(JFFFFFFF)Z", (void*)android_view_RenderNode_stretch}, + {"nStretch", "(JFFFFFFFF)Z", (void*)android_view_RenderNode_stretch}, {"nHasShadow", "(J)Z", (void*)android_view_RenderNode_hasShadow}, {"nSetSpotShadowColor", "(JI)Z", (void*)android_view_RenderNode_setSpotShadowColor}, {"nGetSpotShadowColor", "(J)I", (void*)android_view_RenderNode_getSpotShadowColor}, diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 5dc02e8454ac..adf4aee8b931 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -20,6 +20,7 @@ #include "CanvasContext.h" #include "DeviceInfo.h" #include "EglManager.h" +#include "Properties.h" #include "Readback.h" #include "RenderProxy.h" #include "VulkanManager.h" @@ -40,6 +41,7 @@ #include <utils/Mutex.h> #include <thread> +#include <android-base/properties.h> #include <ui/FatVector.h> namespace android { @@ -251,6 +253,11 @@ void RenderThread::requireVkContext() { void RenderThread::initGrContextOptions(GrContextOptions& options) { options.fPreferExternalImagesOverES3 = true; options.fDisableDistanceFieldPaths = true; + if (android::base::GetBoolProperty(PROPERTY_REDUCE_OPS_TASK_SPLITTING, false)) { + options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kYes; + } else { + options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo; + } } void RenderThread::destroyRenderingContext() { diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index e93824dfbd30..01126860b3ba 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -336,6 +336,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe GET_DEV_PROC(ResetCommandBuffer); GET_DEV_PROC(ResetFences); GET_DEV_PROC(WaitForFences); + GET_DEV_PROC(FrameBoundaryANDROID); } void VulkanManager::initialize() { @@ -516,6 +517,25 @@ void VulkanManager::finishFrame(SkSurface* surface) { if (semaphore != VK_NULL_HANDLE) { if (submitted == GrSemaphoresSubmitted::kYes) { mSwapSemaphore = semaphore; + if (mFrameBoundaryANDROID) { + // retrieve VkImage used as render target + VkImage image = VK_NULL_HANDLE; + GrBackendRenderTarget backendRenderTarget = + surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess); + if (backendRenderTarget.isValid()) { + GrVkImageInfo info; + if (backendRenderTarget.getVkImageInfo(&info)) { + image = info.fImage; + } else { + ALOGE("Frame boundary: backend is not vulkan"); + } + } else { + ALOGE("Frame boundary: invalid backend render target"); + } + // frameBoundaryANDROID needs to know about mSwapSemaphore, but + // it won't wait on it. + mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image); + } } else { destroy_semaphore(mDestroySemaphoreContext); mDestroySemaphoreContext = nullptr; diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 0912369b611d..7b5fe19c64f5 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -31,6 +31,21 @@ #include <vk/GrVkExtensions.h> #include <vulkan/vulkan.h> +// VK_ANDROID_frame_boundary is a bespoke extension defined by AGI +// (https://github.com/google/agi) to enable profiling of apps rendering via +// HWUI. This extension is not defined in Khronos, hence the need to declare it +// manually here. There's a superseding extension (VK_EXT_frame_boundary) being +// discussed in Khronos, but in the meantime we use the bespoke +// VK_ANDROID_frame_boundary. This is a device extension that is implemented by +// AGI's Vulkan capture layer, such that it is only supported by devices when +// AGI is doing a capture of the app. +// +// TODO(b/182165045): use the Khronos blessed VK_EXT_frame_boudary once it has +// landed in the spec. +typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore semaphore, + VkImage image); +#define VK_ANDROID_FRAME_BOUNDARY_EXTENSION_NAME "VK_ANDROID_frame_boundary" + #include "Frame.h" #include "IRenderPipeline.h" #include "VulkanSurface.h" @@ -160,6 +175,7 @@ private: VkPtr<PFN_vkDestroyFence> mDestroyFence; VkPtr<PFN_vkWaitForFences> mWaitForFences; VkPtr<PFN_vkResetFences> mResetFences; + VkPtr<PFN_vkFrameBoundaryANDROID> mFrameBoundaryANDROID; VkInstance mInstance = VK_NULL_HANDLE; VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index 1a3dbe7faaf6..ab23448ab93f 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -33,7 +33,7 @@ namespace { constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf"; -constexpr char kRegularFont[] = "/system/fonts/NotoSerif.ttf"; +constexpr char kRegularFont[] = "/system/fonts/NotoSerif-Regular.ttf"; constexpr char kBoldFont[] = "/system/fonts/NotoSerif-Bold.ttf"; constexpr char kItalicFont[] = "/system/fonts/NotoSerif-Italic.ttf"; constexpr char kBoldItalicFont[] = "/system/fonts/NotoSerif-BoldItalic.ttf"; |