diff options
57 files changed, 2307 insertions, 496 deletions
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml index bb57161b4f6b..4e24909f5d3b 100644 --- a/apct-tests/perftests/core/AndroidManifest.xml +++ b/apct-tests/perftests/core/AndroidManifest.xml @@ -16,6 +16,7 @@ <application> <uses-library android:name="android.test.runner" /> + <profileable android:shell="true" /> <activity android:name="android.perftests.utils.PerfTestActivity" android:exported="true"> <intent-filter> diff --git a/api/Android.mk b/api/Android.mk new file mode 100644 index 000000000000..ce5f995033c5 --- /dev/null +++ b/api/Android.mk @@ -0,0 +1,2 @@ +.PHONY: checkapi +checkapi: frameworks-base-api-current-compat frameworks-base-api-system-current-compat frameworks-base-api-module-lib-current-compat diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index fb0cac3ba3f4..9322bf6deefb 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -449,6 +449,7 @@ public final class SurfaceControl implements Parcelable { private String mName; /** + * Note: do not rename, this field is used by native code. * @hide */ public long mNativeObject; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b6f775d3e536..ec1f73f648cf 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -286,7 +286,7 @@ public final class ViewRootImpl implements ViewParent, * @hide */ public static final boolean LOCAL_LAYOUT = - SystemProperties.getBoolean("persist.debug.local_layout", false); + SystemProperties.getBoolean("persist.debug.local_layout", true); /** * Set this system property to true to force the view hierarchy to render diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index 56e910769cb5..e2c8a31cc987 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -188,6 +188,10 @@ public final class TaskFragmentInfo implements Parcelable { /** * Returns {@code true} if the parameters that are important for task fragment organizers are * equal between this {@link TaskFragmentInfo} and {@param that}. + * Note that this method is usually called with + * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer( + * Configuration, Configuration)} to determine if this {@link TaskFragmentInfo} should + * be dispatched to the client. */ public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) { if (that == null) { diff --git a/core/java/android/window/TaskFragmentParentInfo.aidl b/core/java/android/window/TaskFragmentParentInfo.aidl new file mode 100644 index 000000000000..79d2209ab244 --- /dev/null +++ b/core/java/android/window/TaskFragmentParentInfo.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * The information about the parent Task of a particular TaskFragment + * @hide + */ +parcelable TaskFragmentParentInfo;
\ No newline at end of file diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java new file mode 100644 index 000000000000..64b2638407df --- /dev/null +++ b/core/java/android/window/TaskFragmentParentInfo.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.WindowConfiguration; +import android.content.res.Configuration; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The information about the parent Task of a particular TaskFragment + * @hide + */ +public class TaskFragmentParentInfo implements Parcelable { + @NonNull + private final Configuration mConfiguration = new Configuration(); + + private final int mDisplayId; + + private final boolean mVisibleRequested; + + public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId, + boolean visibleRequested) { + mConfiguration.setTo(configuration); + mDisplayId = displayId; + mVisibleRequested = visibleRequested; + } + + public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { + mConfiguration.setTo(info.getConfiguration()); + mDisplayId = info.mDisplayId; + mVisibleRequested = info.mVisibleRequested; + } + + /** The {@link Configuration} of the parent Task */ + @NonNull + public Configuration getConfiguration() { + return mConfiguration; + } + + /** + * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the + * Task is detached from previously associated display. + */ + public int getDisplayId() { + return mDisplayId; + } + + /** Whether the parent Task is requested to be visible or not */ + public boolean isVisibleRequested() { + return mVisibleRequested; + } + + /** + * Returns {@code true} if the parameters which are important for task fragment + * organizers are equal between this {@link TaskFragmentParentInfo} and {@code that}. + * Note that this method is usually called with + * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer( + * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should + * be dispatched to the client. + */ + public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) { + if (that == null) { + return false; + } + return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId + && mVisibleRequested == that.mVisibleRequested; + } + + @WindowConfiguration.WindowingMode + private int getWindowingMode() { + return mConfiguration.windowConfiguration.getWindowingMode(); + } + + @Override + public String toString() { + return TaskFragmentParentInfo.class.getSimpleName() + ":{" + + "config=" + mConfiguration + + ", displayId=" + mDisplayId + + ", visibleRequested=" + mVisibleRequested + + "}"; + } + + /** + * Indicates that whether this {@link TaskFragmentParentInfo} equals to {@code obj}. + * Note that {@link #equalsForTaskFragmentOrganizer(TaskFragmentParentInfo)} should be used + * for most cases because not all {@link Configuration} properties are interested for + * {@link TaskFragmentOrganizer}. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TaskFragmentParentInfo)) { + return false; + } + final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj; + return mConfiguration.equals(that.mConfiguration) + && mDisplayId == that.mDisplayId + && mVisibleRequested == that.mVisibleRequested; + } + + @Override + public int hashCode() { + int result = mConfiguration.hashCode(); + result = 31 * result + mDisplayId; + result = 31 * result + (mVisibleRequested ? 1 : 0); + return result; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + mConfiguration.writeToParcel(dest, flags); + dest.writeInt(mDisplayId); + dest.writeBoolean(mVisibleRequested); + } + + private TaskFragmentParentInfo(Parcel in) { + mConfiguration.readFromParcel(in); + mDisplayId = in.readInt(); + mVisibleRequested = in.readBoolean(); + } + + public static final Creator<TaskFragmentParentInfo> CREATOR = + new Creator<TaskFragmentParentInfo>() { + @Override + public TaskFragmentParentInfo createFromParcel(Parcel in) { + return new TaskFragmentParentInfo(in); + } + + @Override + public TaskFragmentParentInfo[] newArray(int size) { + return new TaskFragmentParentInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java index 04fcd3afcf1d..76677431d6fa 100644 --- a/core/java/android/window/TaskFragmentTransaction.java +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -173,10 +173,6 @@ public final class TaskFragmentTransaction implements Parcelable { /** @see #setTaskId(int) */ private int mTaskId; - /** @see #setTaskConfiguration(Configuration) */ - @Nullable - private Configuration mTaskConfiguration; - /** @see #setErrorCallbackToken(IBinder) */ @Nullable private IBinder mErrorCallbackToken; @@ -193,6 +189,9 @@ public final class TaskFragmentTransaction implements Parcelable { @Nullable private IBinder mActivityToken; + @Nullable + private TaskFragmentParentInfo mTaskFragmentParentInfo; + public Change(@ChangeType int type) { mType = type; } @@ -202,11 +201,11 @@ public final class TaskFragmentTransaction implements Parcelable { mTaskFragmentToken = in.readStrongBinder(); mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR); mTaskId = in.readInt(); - mTaskConfiguration = in.readTypedObject(Configuration.CREATOR); mErrorCallbackToken = in.readStrongBinder(); mErrorBundle = in.readBundle(TaskFragmentTransaction.class.getClassLoader()); mActivityIntent = in.readTypedObject(Intent.CREATOR); mActivityToken = in.readStrongBinder(); + mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR); } @Override @@ -215,11 +214,11 @@ public final class TaskFragmentTransaction implements Parcelable { dest.writeStrongBinder(mTaskFragmentToken); dest.writeTypedObject(mTaskFragmentInfo, flags); dest.writeInt(mTaskId); - dest.writeTypedObject(mTaskConfiguration, flags); dest.writeStrongBinder(mErrorCallbackToken); dest.writeBundle(mErrorBundle); dest.writeTypedObject(mActivityIntent, flags); dest.writeStrongBinder(mActivityToken); + dest.writeTypedObject(mTaskFragmentParentInfo, flags); } /** The change is related to the TaskFragment created with this unique token. */ @@ -243,10 +242,10 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } + // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release. /** Configuration of the parent Task. */ @NonNull public Change setTaskConfiguration(@NonNull Configuration configuration) { - mTaskConfiguration = requireNonNull(configuration); return this; } @@ -294,6 +293,19 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } + // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release. + /** + * Sets info of the parent Task of the embedded TaskFragment. + * @see TaskFragmentParentInfo + * + * @hide pending unhide + */ + @NonNull + public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { + mTaskFragmentParentInfo = requireNonNull(info); + return this; + } + @ChangeType public int getType() { return mType; @@ -313,9 +325,10 @@ public final class TaskFragmentTransaction implements Parcelable { return mTaskId; } + // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release. @Nullable public Configuration getTaskConfiguration() { - return mTaskConfiguration; + return mTaskFragmentParentInfo.getConfiguration(); } @Nullable @@ -339,6 +352,13 @@ public final class TaskFragmentTransaction implements Parcelable { return mActivityToken; } + // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release. + /** @hide pending unhide */ + @Nullable + public TaskFragmentParentInfo getTaskFragmentParentInfo() { + return mTaskFragmentParentInfo; + } + @Override public String toString() { return "Change{ type=" + mType + " }"; diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java index 354eb62ba045..be0b729aedfe 100644 --- a/core/java/com/android/internal/app/SimpleIconFactory.java +++ b/core/java/com/android/internal/app/SimpleIconFactory.java @@ -51,6 +51,7 @@ import android.util.Pools.SynchronizedPool; import android.util.TypedValue; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import org.xmlpull.v1.XmlPullParser; @@ -69,6 +70,7 @@ public class SimpleIconFactory { private static final SynchronizedPool<SimpleIconFactory> sPool = new SynchronizedPool<>(Runtime.getRuntime().availableProcessors()); + private static boolean sPoolEnabled = true; private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE; private static final float BLUR_FACTOR = 1.5f / 48; @@ -92,7 +94,7 @@ public class SimpleIconFactory { */ @Deprecated public static SimpleIconFactory obtain(Context ctx) { - SimpleIconFactory instance = sPool.acquire(); + SimpleIconFactory instance = sPoolEnabled ? sPool.acquire() : null; if (instance == null) { final ActivityManager am = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE); final int iconDpi = (am == null) ? 0 : am.getLauncherLargeIconDensity(); @@ -106,6 +108,17 @@ public class SimpleIconFactory { return instance; } + /** + * Enables or disables SimpleIconFactory objects pooling. It is enabled in production, you + * could use this method in tests and disable the pooling to make the icon rendering more + * deterministic because some sizing parameters will not be cached. Please ensure that you + * reset this value back after finishing the test. + */ + @VisibleForTesting + public static void setPoolEnabled(boolean poolEnabled) { + sPoolEnabled = poolEnabled; + } + private static int getAttrDimFromContext(Context ctx, @AttrRes int attrId, String errorMsg) { final Resources res = ctx.getResources(); TypedValue outVal = new TypedValue(); diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index b1e7d15cbf4a..deafd19e866f 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -1001,16 +1001,24 @@ public final class Zygote { } /** + * This will enable jdwp by default for all apps. It is OK to cache this property + * because we expect to reboot the system whenever this property changes + */ + private static final boolean ENABLE_JDWP = SystemProperties.get( + "persist.debug.dalvik.vm.jdwp.enabled").equals("1"); + + /** * Applies debugger system properties to the zygote arguments. * - * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, - * the debugger state is specified via the "--enable-jdwp" flag - * in the spawn request. + * For eng builds all apps are debuggable. On userdebug and user builds + * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are + * debuggable. Otherwise, the debugger state is specified via the + * "--enable-jdwp" flag in the spawn request. * * @param args non-null; zygote spawner args */ static void applyDebuggerSystemProperty(ZygoteArguments args) { - if (RoSystemProperties.DEBUGGABLE) { + if (Build.IS_ENG || ENABLE_JDWP) { args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; } } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index eb8e4fc0f533..65c2d00fda06 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1507,7 +1507,8 @@ public class LockPatternUtils { STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, - STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT}) + STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT, + SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED}) @Retention(RetentionPolicy.SOURCE) public @interface StrongAuthFlags {} @@ -1560,6 +1561,12 @@ public class LockPatternUtils { public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80; /** + * Some authentication is required because the trustagent either timed out or was disabled + * manually. + */ + public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100; + + /** * Strong auth flags that do not prevent biometric methods from being accepted as auth. * If any other flags are set, biometric authentication is disabled. */ diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index aefec6cbc2cb..4ad995adaa11 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -28,6 +28,7 @@ #include <android_runtime/android_graphics_GraphicBuffer.h> #include <android_runtime/android_hardware_HardwareBuffer.h> #include <android_runtime/android_view_Surface.h> +#include <android_runtime/android_view_SurfaceControl.h> #include <android_runtime/android_view_SurfaceSession.h> #include <gui/ISurfaceComposer.h> #include <gui/Surface.h> @@ -221,8 +222,14 @@ static struct { static struct { jclass clazz; + jfieldID mNativeObject; +} gTransactionClassInfo; + +static struct { + jclass clazz; + jfieldID mNativeObject; jmethodID invokeReleaseCallback; -} gInvokeReleaseCallback; +} gSurfaceControlClassInfo; constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) { switch (colorMode) { @@ -511,10 +518,11 @@ static ReleaseBufferCallback genReleaseCallback(JNIEnv* env, jobject releaseCall if (fenceCopy) { fenceCopy->incStrong(0); } - globalCallbackRef->env()->CallStaticVoidMethod(gInvokeReleaseCallback.clazz, - gInvokeReleaseCallback.invokeReleaseCallback, - globalCallbackRef->object(), - reinterpret_cast<jlong>(fenceCopy)); + globalCallbackRef->env() + ->CallStaticVoidMethod(gSurfaceControlClassInfo.clazz, + gSurfaceControlClassInfo.invokeReleaseCallback, + globalCallbackRef->object(), + reinterpret_cast<jlong>(fenceCopy)); }; } @@ -1906,6 +1914,28 @@ static jobject nativeGetDefaultApplyToken(JNIEnv* env, jclass clazz) { // ---------------------------------------------------------------------------- +SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env, + jobject surfaceControlObj) { + if (!!surfaceControlObj && + env->IsInstanceOf(surfaceControlObj, gSurfaceControlClassInfo.clazz)) { + return reinterpret_cast<SurfaceControl*>( + env->GetLongField(surfaceControlObj, gSurfaceControlClassInfo.mNativeObject)); + } else { + return nullptr; + } +} + +SurfaceComposerClient::Transaction* android_view_SurfaceTransaction_getNativeSurfaceTransaction( + JNIEnv* env, jobject surfaceTransactionObj) { + if (!!surfaceTransactionObj && + env->IsInstanceOf(surfaceTransactionObj, gTransactionClassInfo.clazz)) { + return reinterpret_cast<SurfaceComposerClient::Transaction*>( + env->GetLongField(surfaceTransactionObj, gTransactionClassInfo.mNativeObject)); + } else { + return nullptr; + } +} + static const JNINativeMethod sSurfaceControlMethods[] = { // clang-format off {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", @@ -2306,11 +2336,18 @@ int register_android_view_SurfaceControl(JNIEnv* env) GetFieldIDOrDie(env, displayDecorationSupportClazz, "alphaInterpretation", "I"); jclass surfaceControlClazz = FindClassOrDie(env, "android/view/SurfaceControl"); - gInvokeReleaseCallback.clazz = MakeGlobalRefOrDie(env, surfaceControlClazz); - gInvokeReleaseCallback.invokeReleaseCallback = + gSurfaceControlClassInfo.clazz = MakeGlobalRefOrDie(env, surfaceControlClazz); + gSurfaceControlClassInfo.mNativeObject = + GetFieldIDOrDie(env, gSurfaceControlClassInfo.clazz, "mNativeObject", "J"); + gSurfaceControlClassInfo.invokeReleaseCallback = GetStaticMethodIDOrDie(env, surfaceControlClazz, "invokeReleaseCallback", "(Ljava/util/function/Consumer;J)V"); + jclass surfaceTransactionClazz = FindClassOrDie(env, "android/view/SurfaceControl$Transaction"); + gTransactionClassInfo.clazz = MakeGlobalRefOrDie(env, surfaceTransactionClazz); + gTransactionClassInfo.mNativeObject = + GetFieldIDOrDie(env, gTransactionClassInfo.clazz, "mNativeObject", "J"); + return err; } diff --git a/core/jni/include/android_runtime/android_view_SurfaceControl.h b/core/jni/include/android_runtime/android_view_SurfaceControl.h new file mode 100644 index 000000000000..10a754903208 --- /dev/null +++ b/core/jni/include/android_runtime/android_view_SurfaceControl.h @@ -0,0 +1,38 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_VIEW_SURFACECONTROL_H +#define _ANDROID_VIEW_SURFACECONTROL_H + +#include <gui/SurfaceComposerClient.h> +#include <gui/SurfaceControl.h> + +#include "jni.h" + +namespace android { + +/* Gets the underlying native SurfaceControl for a java SurfaceControl. */ +extern SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl( + JNIEnv* env, jobject surfaceControlObj); + +/* Gets the underlying native SurfaceControl for a java SurfaceControl. */ +extern SurfaceComposerClient::Transaction* +android_view_SurfaceTransaction_getNativeSurfaceTransaction(JNIEnv* env, + jobject surfaceTransactionObj); + +} // namespace android + +#endif // _ANDROID_VIEW_SURFACECONTROL_H diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1d2ce7eb37c3..060f440c9c4f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1662,13 +1662,18 @@ darkening hysteresis constraint value is the n-th element of config_screenDarkeningThresholds. + Historically, it has been assumed that this will be an integer array with values in the + range of [0, 255]. However, it is now assumed to be a float array with values in the + range of [0, 1]. To accommodate both the possibilities, we internally check the scale on + which the thresholds are defined, and calibrate it accordingly. + The (zero-based) index is calculated as follows: (MAX is the largest index of the array) condition calculated index value < level[0] 0 level[n] <= value < level[n+1] n+1 level[MAX] <= value MAX+1 --> - <integer-array name="config_screenThresholdLevels"> - </integer-array> + <array name="config_screenThresholdLevels"> + </array> <!-- Array of hysteresis constraint values for brightening, represented as tenths of a percent. The length of this array is assumed to be one greater than diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index febd7917dff9..74303e2fab7c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -31,6 +31,7 @@ import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -93,6 +94,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { } /** No longer overrides the animation if the transition is on the given Task. */ + @GuardedBy("mLock") void stopOverrideSplitAnimation(int taskId) { if (mAnimationController != null) { mAnimationController.unregisterRemoteAnimations(taskId); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index c8ac0fc73ff9..00be5a6e3416 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -17,8 +17,10 @@ package androidx.window.extensions.embedding; import android.app.Activity; +import android.content.res.Configuration; import android.util.Pair; import android.util.Size; +import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -32,14 +34,18 @@ class SplitContainer { private final TaskFragmentContainer mSecondaryContainer; @NonNull private final SplitRule mSplitRule; + @NonNull + private SplitAttributes mSplitAttributes; SplitContainer(@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, - @NonNull SplitRule splitRule) { + @NonNull SplitRule splitRule, + @NonNull SplitAttributes splitAttributes) { mPrimaryContainer = primaryContainer; mSecondaryContainer = secondaryContainer; mSplitRule = splitRule; + mSplitAttributes = splitAttributes; if (shouldFinishPrimaryWithSecondary(splitRule)) { if (mPrimaryContainer.getRunningActivityCount() == 1 @@ -72,6 +78,26 @@ class SplitContainer { return mSplitRule; } + @NonNull + SplitAttributes getSplitAttributes() { + return mSplitAttributes; + } + + /** + * Updates the {@link SplitAttributes} to this container. + * It is usually used when there's a folding state change or + * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int, + * Configuration)}. + */ + void setSplitAttributes(@NonNull SplitAttributes splitAttributes) { + mSplitAttributes = splitAttributes; + } + + @NonNull + TaskContainer getTaskContainer() { + return getPrimaryContainer().getTaskContainer(); + } + /** Returns the minimum dimension pair of primary container and secondary container. */ @NonNull Pair<Size, Size> getMinDimensionsPair() { @@ -141,6 +167,7 @@ class SplitContainer { + " primaryContainer=" + mPrimaryContainer + " secondaryContainer=" + mSecondaryContainer + " splitRule=" + mSplitRule + + " splitAttributes" + mSplitAttributes + "}"; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 126f8350839c..203ece091e46 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -19,6 +19,7 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; @@ -40,12 +41,13 @@ import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAs import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds; -import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; +import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityOptions; import android.app.ActivityThread; +import android.app.Application; import android.app.Instrumentation; import android.content.ComponentName; import android.content.Context; @@ -62,19 +64,25 @@ import android.util.Log; import android.util.Pair; import android.util.Size; import android.util.SparseArray; +import android.view.WindowMetrics; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.window.common.CommonFoldingFeature; import androidx.window.common.EmptyLifecycleCallbacksAdapter; +import androidx.window.extensions.WindowExtensionsProvider; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -96,6 +104,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); /** + * A developer-defined {@link SplitAttributes} calculator to compute the current + * {@link SplitAttributes} with the current device and window states. + * It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)} + * and unregistered via {@link #clearSplitAttributesCalculator()}. + * This is called when: + * <ul> + * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer, + * WindowContainerTransaction)}</li> + * <li>There's a started Activity which matches {@link SplitPairRule} </li> + * <li>Checking whether the place holder should be launched if there's a Activity matches + * {@link SplitPlaceholderRule} </li> + * </ul> + */ + @GuardedBy("mLock") + @Nullable + private SplitAttributesCalculator mSplitAttributesCalculator; + /** * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info * below it. * When the app is host of multiple Tasks, there can be multiple splits controlled by the same @@ -105,26 +130,65 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>(); - // Callback to Jetpack to notify about changes to split states. - @NonNull + /** Callback to Jetpack to notify about changes to split states. */ + @Nullable private Consumer<List<SplitInfo>> mEmbeddingCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final Handler mHandler; final Object mLock = new Object(); private final ActivityStartMonitor mActivityStartMonitor; + @NonNull + final WindowLayoutComponentImpl mWindowLayoutComponent; public SplitController() { + this((WindowLayoutComponentImpl) Objects.requireNonNull(WindowExtensionsProvider + .getWindowExtensions().getWindowLayoutComponent())); + } + + @VisibleForTesting + SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent) { final MainThreadExecutor executor = new MainThreadExecutor(); mHandler = executor.mHandler; mPresenter = new SplitPresenter(executor, this); - ActivityThread activityThread = ActivityThread.currentActivityThread(); + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + final Application application = activityThread.getApplication(); // Register a callback to be notified about activities being created. - activityThread.getApplication().registerActivityLifecycleCallbacks( - new LifecycleCallbacks()); + application.registerActivityLifecycleCallbacks(new LifecycleCallbacks()); // Intercept activity starts to route activities to new containers if necessary. Instrumentation instrumentation = activityThread.getInstrumentation(); + mActivityStartMonitor = new ActivityStartMonitor(); instrumentation.addMonitor(mActivityStartMonitor); + mWindowLayoutComponent = windowLayoutComponent; + mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener()); + } + + private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> { + @Override + public void accept(List<CommonFoldingFeature> foldingFeatures) { + synchronized (mLock) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + for (int i = 0; i < mTaskContainers.size(); i++) { + final TaskContainer taskContainer = mTaskContainers.valueAt(i); + if (!taskContainer.isVisible()) { + continue; + } + if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) { + continue; + } + // TODO(b/238948678): Support reporting display features in all windowing modes. + if (taskContainer.isInMultiWindow()) { + continue; + } + if (taskContainer.isEmpty()) { + continue; + } + updateContainersInTask(wct, taskContainer); + updateAnimationOverride(taskContainer); + } + mPresenter.applyTransaction(wct); + } + } } /** Updates the embedding rules applied to future activity launches. */ @@ -141,12 +205,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) { - // TODO: Implement this method + synchronized (mLock) { + mSplitAttributesCalculator = calculator; + } } @Override public void clearSplitAttributesCalculator() { - // TODO: Implement this method + synchronized (mLock) { + mSplitAttributesCalculator = null; + } + } + + @GuardedBy("mLock") + @Nullable + SplitAttributesCalculator getSplitAttributesCalculator() { + return mSplitAttributesCalculator; } @NonNull @@ -191,7 +265,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen onTaskFragmentVanished(wct, info); break; case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: - onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration()); + onTaskFragmentParentInfoChanged(wct, taskId, + change.getTaskFragmentParentInfo()); break; case TYPE_TASK_FRAGMENT_ERROR: final Bundle errorBundle = change.getErrorBundle(); @@ -346,22 +421,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskId Id of the parent Task that is changed. - * @param parentConfig Config of the parent Task. + * @param parentInfo {@link TaskFragmentParentInfo} of the parent Task. */ @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, - int taskId, @NonNull Configuration parentConfig) { - onTaskConfigurationChanged(taskId, parentConfig); - if (isInPictureInPicture(parentConfig)) { - // No need to update presentation in PIP until the Task exit PIP. - return; - } + int taskId, @NonNull TaskFragmentParentInfo parentInfo) { final TaskContainer taskContainer = getTaskContainer(taskId); if (taskContainer == null || taskContainer.isEmpty()) { Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId); return; } + taskContainer.updateTaskFragmentParentInfo(parentInfo); + if (!taskContainer.isVisible()) { + // Don't update containers if the task is not visible. We only update containers when + // parentInfo#isVisibleRequested is true. + return; + } + onTaskContainerInfoChanged(taskContainer, parentInfo.getConfiguration()); + if (isInPictureInPicture(parentInfo.getConfiguration())) { + // No need to update presentation in PIP until the Task exit PIP. + return; + } + updateContainersInTask(wct, taskContainer); + } + + private void updateContainersInTask(@NonNull WindowContainerTransaction wct, + @NonNull TaskContainer taskContainer) { // Update all TaskFragments in the Task. Make a copy of the list since some may be // removed on updating. final List<TaskFragmentContainer> containers = @@ -486,6 +572,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */ + @GuardedBy("mLock") private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final TaskContainer taskContainer = mTaskContainers.valueAt(i); @@ -501,14 +588,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - private void onTaskConfigurationChanged(int taskId, @NonNull Configuration config) { - final TaskContainer taskContainer = mTaskContainers.get(taskId); - if (taskContainer == null) { - return; - } + @GuardedBy("mLock") + private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer, + @NonNull Configuration config) { final boolean wasInPip = taskContainer.isInPictureInPicture(); final boolean isInPIp = isInPictureInPicture(config); - taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode()); // We need to check the animation override when enter/exit PIP or has bounds changed. boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; @@ -526,37 +610,49 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Updates if we should override transition animation. We only want to override if the Task * bounds is large enough for at least one split rule. */ + @GuardedBy("mLock") private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { if (ENABLE_SHELL_TRANSITIONS) { // TODO(b/207070762): cleanup with legacy app transition // Animation will be handled by WM Shell with Shell transition enabled. return; } - if (!taskContainer.isTaskBoundsInitialized() - || !taskContainer.isWindowingModeInitialized()) { + if (!taskContainer.isTaskBoundsInitialized()) { // We don't know about the Task bounds/windowingMode yet. return; } - // We only want to override if it supports split. - if (supportSplit(taskContainer)) { + // We only want to override if the TaskContainer may show split. + if (mayShowSplit(taskContainer)) { mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId()); } else { mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId()); } } - private boolean supportSplit(@NonNull TaskContainer taskContainer) { + /** Returns whether the given {@link TaskContainer} may show in split. */ + // Suppress GuardedBy warning because lint asks to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") + private boolean mayShowSplit(@NonNull TaskContainer taskContainer) { // No split inside PIP. if (taskContainer.isInPictureInPicture()) { return false; } + // Always assume the TaskContainer if SplitAttributesCalculator is set + if (mSplitAttributesCalculator != null) { + return true; + } // Check if the parent container bounds can support any split rule. for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof SplitRule)) { continue; } - if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) { + final SplitRule splitRule = (SplitRule) rule; + final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes( + taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */); + if (shouldShowSplit(splitAttributes)) { return true; } } @@ -700,14 +796,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Starts an activity to side of the launchingActivity with the provided split config. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @GuardedBy("mLock") private void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, - @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) { + @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback, + boolean isPlaceholder) { try { mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule, - isPlaceholder); + splitAttributes, isPlaceholder); } catch (Exception e) { if (failureCallback != null) { failureCallback.accept(e); @@ -734,6 +834,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** Whether the given new launched activity is in a split with a rule matched. */ + // Suppress GuardedBy warning because lint asks to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) { final TaskFragmentContainer container = getContainerWithActivity(launchedActivity); final SplitContainer splitContainer = getActiveSplitForContainer(container); @@ -827,8 +931,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer primaryContainer = getContainerWithActivity( primaryActivity); final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); + final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity); if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() - && canReuseContainer(splitRule, splitContainer.getSplitRule())) { + && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) { // Can launch in the existing secondary container if the rules share the same // presentation. final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); @@ -958,6 +1063,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @VisibleForTesting @Nullable + @GuardedBy("mLock") TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) { /* @@ -1020,6 +1126,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into. */ + @GuardedBy("mLock") @Nullable private TaskFragmentContainer createEmptyExpandedContainer( @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @@ -1061,8 +1168,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity); final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer); + final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity); if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() - && (canReuseContainer(splitRule, splitContainer.getSplitRule()) + && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics) // TODO(b/231845476) we should always respect clearTop. || !respectClearTop) && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, @@ -1101,12 +1209,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId); } + @GuardedBy("mLock") TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, @NonNull Activity activityInTask, int taskId) { return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, activityInTask, taskId); } + @GuardedBy("mLock") TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, @@ -1130,7 +1240,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen throw new IllegalArgumentException("activityInTask must not be null,"); } if (!mTaskContainers.contains(taskId)) { - mTaskContainers.put(taskId, new TaskContainer(taskId)); + mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask)); } final TaskContainer taskContainer = mTaskContainers.get(taskId); final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, @@ -1142,10 +1252,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } } - if (!taskContainer.isWindowingModeInitialized()) { - taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration() - .windowConfiguration.getWindowingMode()); - } updateAnimationOverride(taskContainer); return container; } @@ -1154,12 +1260,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Creates and registers a new split with the provided containers and configuration. Finishes * existing secondary containers if found for the given primary container. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") void registerSplit(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, - @NonNull SplitRule splitRule) { + @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) { final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, - secondaryContainer, splitRule); + secondaryContainer, splitRule, splitAttributes); // Remove container later to prevent pinning escaping toast showing in lock task mode. if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { removeExistingSecondaryContainers(wct, primaryContainer); @@ -1310,6 +1420,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Skip position update - one or both containers are finished. return; } + final TaskContainer taskContainer = splitContainer.getTaskContainer(); + final SplitRule splitRule = splitContainer.getSplitRule(); + final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); + final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes( + taskContainer.getTaskProperties(), splitRule, minDimensionsPair); + splitContainer.setSplitAttributes(splitAttributes); if (dismissPlaceholderIfNecessary(wct, splitContainer)) { // Placeholder was finished, the positions will be updated when its container is emptied return; @@ -1383,6 +1499,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */); } + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @GuardedBy("mLock") boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, @NonNull Activity activity, boolean isOnCreated) { @@ -1409,18 +1528,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } + final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity); final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity, placeholderRule.getPlaceholderIntent()); - if (!shouldShowSideBySide( - mPresenter.getParentContainerBounds(activity), placeholderRule, - minDimensionsPair)) { + final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties, + placeholderRule, minDimensionsPair); + if (!SplitPresenter.shouldShowSplit(splitAttributes)) { return false; } // TODO(b/190433398): Handle failed request final Bundle options = getPlaceholderOptions(activity, isOnCreated); startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options, - placeholderRule, null /* failureCallback */, true /* isPlaceholder */); + placeholderRule, splitAttributes, null /* failureCallback */, + true /* isPlaceholder */); return true; } @@ -1445,6 +1566,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return options.toBundle(); } + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @VisibleForTesting @GuardedBy("mLock") boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, @@ -1457,11 +1581,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // The placeholder should remain after it was first shown. return false; } - - if (shouldShowSideBySide(splitContainer)) { + final SplitAttributes splitAttributes = splitContainer.getSplitAttributes(); + if (SplitPresenter.shouldShowSplit(splitAttributes)) { return false; } - mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(), false /* shouldFinishDependent */); return true; @@ -1471,6 +1594,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns the rule to launch a placeholder for the activity with the provided component name * if it is configured in the split config. */ + @GuardedBy("mLock") private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) { for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof SplitPlaceholderRule)) { @@ -1487,6 +1611,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Notifies listeners about changes to split states if necessary. */ + @GuardedBy("mLock") private void updateCallbackIfNecessary() { if (mEmbeddingCallback == null) { return; @@ -1508,6 +1633,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * null, that indicates that the active split states are in an intermediate state and should * not be reported. */ + @GuardedBy("mLock") @Nullable private List<SplitInfo> getActiveSplitStates() { List<SplitInfo> splitStates = new ArrayList<>(); @@ -1526,20 +1652,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen .toActivityStack(); final ActivityStack secondaryContainer = container.getSecondaryContainer() .toActivityStack(); - final SplitAttributes.SplitType splitType = shouldShowSideBySide(container) - ? new SplitAttributes.SplitType.RatioSplitType( - container.getSplitRule().getSplitRatio()) - : new SplitAttributes.SplitType.ExpandContainersSplitType(); final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer, - // Splits that are not showing side-by-side are reported as having 0 split - // ratio, since by definition in the API the primary container occupies no - // width of the split when covered by the secondary. - // TODO(b/241042437): use v2 APIs for splitAttributes - new SplitAttributes.Builder() - .setSplitType(splitType) - .setLayoutDirection(container.getSplitRule().getLayoutDirection()) - .build() - ); + container.getSplitAttributes()); splitStates.add(splitState); } } @@ -1577,6 +1691,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns a split rule for the provided pair of primary activity and secondary activity intent * if available. */ + @GuardedBy("mLock") @Nullable private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, @NonNull Intent secondaryActivityIntent) { @@ -1595,6 +1710,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Returns a split rule for the provided pair of primary and secondary activities if available. */ + @GuardedBy("mLock") @Nullable private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { @@ -1669,6 +1785,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns {@code true} if an Activity with the provided component name should always be * expanded to occupy full task bounds. Such activity must not be put in a split. */ + @GuardedBy("mLock") private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) { for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof ActivityRule)) { @@ -1694,6 +1811,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * 'sticky' and the placeholder was finished when fully overlapping the primary container. * @return {@code true} if the associated container should be retained (and not be finished). */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer, @NonNull TaskFragmentContainer associatedContainer) { SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer, @@ -1712,7 +1833,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Decide whether the associated container should be retained based on the current // presentation mode. - if (shouldShowSideBySide(splitContainer)) { + if (shouldShowSplit(splitContainer)) { return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); } else { return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); @@ -1905,23 +2026,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if * there is any. */ - private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) { + private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2, + @NonNull WindowMetrics parentWindowMetrics) { if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) { return false; } - return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2); + return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2, + parentWindowMetrics); } /** Whether the two rules have the same presentation. */ - private static boolean haveSamePresentation(@NonNull SplitPairRule rule1, - @NonNull SplitPairRule rule2) { + @VisibleForTesting + static boolean haveSamePresentation(@NonNull SplitPairRule rule1, + @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) { + if (rule1.getTag() != null || rule2.getTag() != null) { + // Tag must be unique if it is set. We don't want to reuse the container if the rules + // have different tags because they can have different SplitAttributes later through + // SplitAttributesCalculator. + return Objects.equals(rule1.getTag(), rule2.getTag()); + } + // If both rules don't have tag, compare all SplitRules' properties that may affect their + // SplitAttributes. // TODO(b/231655482): add util method to do the comparison in SplitPairRule. - return rule1.getSplitRatio() == rule2.getSplitRatio() - && rule1.getLayoutDirection() == rule2.getLayoutDirection() - && rule1.getFinishPrimaryWithSecondary() - == rule2.getFinishPrimaryWithSecondary() - && rule1.getFinishSecondaryWithPrimary() - == rule2.getFinishSecondaryWithPrimary(); + return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes()) + && rule1.checkParentMetrics(parentWindowMetrics) + == rule2.checkParentMetrics(parentWindowMetrics) + && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary() + && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary(); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 2ef8e4c64855..79603233ae14 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -22,11 +22,11 @@ import android.app.Activity; import android.app.ActivityThread; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; -import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -42,9 +42,21 @@ import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.window.extensions.embedding.SplitAttributes.SplitType; +import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType; +import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType; +import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType; +import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttributesCalculatorParams; +import androidx.window.extensions.embedding.TaskContainer.TaskProperties; +import androidx.window.extensions.layout.DisplayFeature; +import androidx.window.extensions.layout.FoldingFeature; +import androidx.window.extensions.layout.WindowLayoutInfo; import com.android.internal.annotations.VisibleForTesting; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -66,11 +78,25 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { }) private @interface Position {} + private static final int CONTAINER_POSITION_LEFT = 0; + private static final int CONTAINER_POSITION_TOP = 1; + private static final int CONTAINER_POSITION_RIGHT = 2; + private static final int CONTAINER_POSITION_BOTTOM = 3; + + @IntDef(value = { + CONTAINER_POSITION_LEFT, + CONTAINER_POSITION_TOP, + CONTAINER_POSITION_RIGHT, + CONTAINER_POSITION_BOTTOM, + }) + private @interface ContainerPosition {} + /** * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, * Activity, Activity, Intent)}. * No need to expand the splitContainer because screen is big enough to - * {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} and minimum dimensions is satisfied. + * {@link #shouldShowSplit(SplitAttributes)} and minimum dimensions is + * satisfied. */ static final int RESULT_NOT_EXPANDED = 0; /** @@ -78,7 +104,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Activity, Activity, Intent)}. * The splitContainer should be expanded. It is usually because minimum dimensions is not * satisfied. - * @see #shouldShowSideBySide(Rect, SplitRule, Pair) + * @see #shouldShowSplit(SplitAttributes) */ static final int RESULT_EXPANDED = 1; /** @@ -101,6 +127,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { }) private @interface ResultCode {} + @VisibleForTesting + static final SplitAttributes EXPAND_CONTAINERS_ATTRIBUTES = + new SplitAttributes.Builder() + .setSplitType(new ExpandContainersSplitType()) + .build(); + private final SplitController mController; SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) { @@ -129,14 +161,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @return The newly created secondary container. */ @NonNull + @GuardedBy("mController.mLock") TaskFragmentContainer createNewSplitWithEmptySideContainer( @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { - final Rect parentBounds = getParentContainerBounds(primaryActivity); + final TaskProperties taskProperties = getTaskProperties(primaryActivity); final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( primaryActivity, secondaryIntent); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - primaryActivity, minDimensionsPair); + final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule, + minDimensionsPair); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, + splitAttributes); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); @@ -144,8 +179,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final int taskId = primaryContainer.getTaskId(); final TaskFragmentContainer secondaryContainer = mController.newContainer( secondaryIntent, primaryActivity, taskId); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, - rule, primaryActivity, minDimensionsPair); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, + splitAttributes); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(secondaryRectBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), @@ -154,9 +189,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, - minDimensionsPair); + splitAttributes); - mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); + mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule, + splitAttributes); return secondaryContainer; } @@ -176,16 +212,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void createNewSplitContainer(@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) { - final Rect parentBounds = getParentContainerBounds(primaryActivity); + final TaskProperties taskProperties = getTaskProperties(primaryActivity); final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - primaryActivity, minDimensionsPair); + final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule, + minDimensionsPair); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, + splitAttributes); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - primaryActivity, minDimensionsPair); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, + splitAttributes); final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( secondaryActivity); TaskFragmentContainer containerToAvoid = primaryContainer; @@ -200,9 +238,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, - minDimensionsPair); + splitAttributes); - mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); + mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule, + splitAttributes); } /** @@ -244,16 +283,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @param rule The split rule to be applied to the container. * @param isPlaceholder Whether the launch is a placeholder. */ + @GuardedBy("mController.mLock") void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull Activity launchingActivity, @NonNull Intent activityIntent, - @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) { - final Rect parentBounds = getParentContainerBounds(launchingActivity); - final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( - launchingActivity, activityIntent); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - launchingActivity, minDimensionsPair); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - launchingActivity, minDimensionsPair); + @Nullable Bundle activityOptions, @NonNull SplitRule rule, + @NonNull SplitAttributes splitAttributes, boolean isPlaceholder) { + final TaskProperties taskProperties = getTaskProperties(launchingActivity); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, + splitAttributes); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, + splitAttributes); TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( launchingActivity); @@ -268,7 +307,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(primaryRectBounds); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, - rule); + rule, splitAttributes); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, activityIntent, activityOptions, rule, windowingMode); @@ -284,22 +323,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @param updatedContainer The task fragment that was updated and caused this split update. * @param wct WindowContainerTransaction that this update should be performed with. */ + @GuardedBy("mController.mLock") void updateSplitContainer(@NonNull SplitContainer splitContainer, @NonNull TaskFragmentContainer updatedContainer, @NonNull WindowContainerTransaction wct) { - // Getting the parent bounds using the updated container - it will have the recent value. - final Rect parentBounds = getParentContainerBounds(updatedContainer); + // Getting the parent configuration using the updated container - it will have the recent + // value. final SplitRule rule = splitContainer.getSplitRule(); final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer(); final Activity activity = primaryContainer.getTopNonFinishingActivity(); if (activity == null) { return; } - final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); - final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - activity, minDimensionsPair); - final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - activity, minDimensionsPair); + final TaskProperties taskProperties = getTaskProperties(updatedContainer); + final SplitAttributes splitAttributes = splitContainer.getSplitAttributes(); + final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, + splitAttributes); + final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, + splitAttributes); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); // Whether the placeholder is becoming side-by-side with the primary from fullscreen. final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer() @@ -311,7 +352,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, - minDimensionsPair); + splitAttributes); if (isPlaceholderBecomingSplit) { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); @@ -323,14 +364,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); } + @GuardedBy("mController.mLock") private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, - @NonNull Pair<Size, Size> minDimensionsPair) { - final Rect parentBounds = getParentContainerBounds(primaryContainer); + @NonNull SplitAttributes splitAttributes) { // Clear adjacent TaskFragments if the container is shown in fullscreen, or the // secondaryContainer could not be finished. - if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) { + if (!shouldShowSplit(splitAttributes)) { setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), null /* secondary */, null /* splitRule */); } else { @@ -416,8 +457,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Expands the split container if the current split bounds are smaller than the Activity or * Intent that is added to the container. * - * @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} - * and if {@link android.window.TaskFragmentInfo} has reported to the client side. + * @return the {@link ResultCode} based on + * {@link #shouldShowSplit(SplitAttributes)} and if + * {@link android.window.TaskFragmentInfo} has reported to the client side. */ @ResultCode int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct, @@ -427,7 +469,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be" + " non-null."); } - final Rect taskBounds = getParentContainerBounds(primaryActivity); final Pair<Size, Size> minDimensionsPair; if (secondaryActivity != null) { minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity); @@ -436,7 +477,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { secondaryIntent); } // Expand the splitContainer if minimum dimensions are not satisfied. - if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) { + final TaskContainer taskContainer = splitContainer.getTaskContainer(); + final SplitAttributes splitAttributes = sanitizeSplitAttributes( + taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(), + minDimensionsPair); + splitContainer.setSplitAttributes(splitAttributes); + if (!shouldShowSplit(splitAttributes)) { // If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment // bounds. Return failure to create a new SplitContainer which fills task bounds. if (splitContainer.getPrimaryContainer().getInfo() == null @@ -450,36 +496,63 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return RESULT_NOT_EXPANDED; } - static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) { - return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */); + static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) { + return shouldShowSplit(splitContainer.getSplitAttributes()); } - static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) { - final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer()); + static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) { + return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType); + } - return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(), - splitContainer.getMinDimensionsPair()); + @GuardedBy("mController.mLock") + @NonNull + SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties, + @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) { + final Configuration taskConfiguration = taskProperties.getConfiguration(); + final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration); + final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator(); + final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes(); + final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics); + if (calculator == null) { + if (!isDefaultMinSizeSatisfied) { + return EXPAND_CONTAINERS_ATTRIBUTES; + } + return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes, + minDimensionsPair); + } + final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent + .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(), + taskConfiguration.windowConfiguration); + final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams( + taskWindowMetrics, taskConfiguration, defaultSplitAttributes, + isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag()); + final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params); + return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair); } - static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule, + /** + * Returns {@link #EXPAND_CONTAINERS_ATTRIBUTES} if the passed {@link SplitAttributes} doesn't + * meet the minimum dimensions set in {@link ActivityInfo.WindowLayout}. Otherwise, returns + * the passed {@link SplitAttributes}. + */ + @NonNull + private SplitAttributes sanitizeSplitAttributes(@NonNull TaskProperties taskProperties, + @NonNull SplitAttributes splitAttributes, @Nullable Pair<Size, Size> minDimensionsPair) { - // TODO(b/190433398): Supply correct insets. - final WindowMetrics parentMetrics = new WindowMetrics(parentBounds, - new WindowInsets(new Rect())); - // Don't show side by side if bounds is not qualified. - if (!rule.checkParentMetrics(parentMetrics)) { - return false; - } - final float splitRatio = rule.getSplitRatio(); - // We only care the size of the bounds regardless of its position. - final Rect primaryBounds = getPrimaryBounds(parentBounds, splitRatio, true /* isLtr */); - final Rect secondaryBounds = getSecondaryBounds(parentBounds, splitRatio, true /* isLtr */); - if (minDimensionsPair == null) { - return true; - } - return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first) - && !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second); + return splitAttributes; + } + final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); + final Configuration taskConfiguration = taskProperties.getConfiguration(); + final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes, + foldingFeature); + final Rect secondaryBounds = getSecondaryBounds(taskConfiguration, splitAttributes, + foldingFeature); + if (boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first) + || boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second)) { + return EXPAND_CONTAINERS_ATTRIBUTES; + } + return splitAttributes; } @NonNull @@ -541,20 +614,25 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @VisibleForTesting @NonNull - static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds, - @NonNull SplitRule rule, @NonNull Activity primaryActivity, - @Nullable Pair<Size, Size> minDimensionsPair) { - if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) { + Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties, + @NonNull SplitAttributes splitAttributes) { + final Configuration taskConfiguration = taskProperties.getConfiguration(); + final FoldingFeature foldingFeature = getFoldingFeature(taskProperties); + final SplitType splitType = computeSplitType(splitAttributes, taskConfiguration, + foldingFeature); + final SplitAttributes computedSplitAttributes = new SplitAttributes.Builder() + .setSplitType(splitType) + .setLayoutDirection(splitAttributes.getLayoutDirection()) + .build(); + if (!shouldShowSplit(computedSplitAttributes)) { return new Rect(); } - final boolean isLtr = isLtr(primaryActivity, rule); - final float splitRatio = rule.getSplitRatio(); - switch (position) { case POSITION_START: - return getPrimaryBounds(parentBounds, splitRatio, isLtr); + return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature); case POSITION_END: - return getSecondaryBounds(parentBounds, splitRatio, isLtr); + return getSecondaryBounds(taskConfiguration, computedSplitAttributes, + foldingFeature); case POSITION_FILL: default: return new Rect(); @@ -562,74 +640,303 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @NonNull - private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio, - boolean isLtr) { - return isLtr ? getLeftContainerBounds(parentBounds, splitRatio) - : getRightContainerBounds(parentBounds, 1 - splitRatio); + private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + if (!shouldShowSplit(splitAttributes)) { + return new Rect(); + } + switch (splitAttributes.getLayoutDirection()) { + case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: { + return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: { + return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.LOCALE: { + final boolean isLtr = taskConfiguration.getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; + return isLtr + ? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature) + : getRightContainerBounds(taskConfiguration, splitAttributes, + foldingFeature); + } + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: { + return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: { + return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + default: + throw new IllegalArgumentException("Unknown layout direction:" + + splitAttributes.getLayoutDirection()); + } } @NonNull - private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio, - boolean isLtr) { - return isLtr ? getRightContainerBounds(parentBounds, splitRatio) - : getLeftContainerBounds(parentBounds, 1 - splitRatio); + private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + if (!shouldShowSplit(splitAttributes)) { + return new Rect(); + } + switch (splitAttributes.getLayoutDirection()) { + case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: { + return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: { + return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.LOCALE: { + final boolean isLtr = taskConfiguration.getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; + return isLtr + ? getRightContainerBounds(taskConfiguration, splitAttributes, + foldingFeature) + : getLeftContainerBounds(taskConfiguration, splitAttributes, + foldingFeature); + } + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: { + return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: { + return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature); + } + default: + throw new IllegalArgumentException("Unknown layout direction:" + + splitAttributes.getLayoutDirection()); + } } - private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) { - return new Rect( - parentBounds.left, - parentBounds.top, - (int) (parentBounds.left + parentBounds.width() * splitRatio), - parentBounds.bottom); + @NonNull + private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, + CONTAINER_POSITION_LEFT, foldingFeature); + final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); + return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom); } - private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) { - return new Rect( - (int) (parentBounds.left + parentBounds.width() * splitRatio), - parentBounds.top, - parentBounds.right, - parentBounds.bottom); + @NonNull + private Rect getRightContainerBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + final int left = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, + CONTAINER_POSITION_RIGHT, foldingFeature); + final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); + return new Rect(left, parentBounds.top, parentBounds.right, parentBounds.bottom); + } + + @NonNull + private Rect getTopContainerBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + final int bottom = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, + CONTAINER_POSITION_TOP, foldingFeature); + final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); + return new Rect(parentBounds.left, parentBounds.top, parentBounds.right, bottom); + } + + @NonNull + private Rect getBottomContainerBounds(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) { + final int top = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes, + CONTAINER_POSITION_BOTTOM, foldingFeature); + final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); + return new Rect(parentBounds.left, top, parentBounds.right, parentBounds.bottom); } /** - * Checks if a split with the provided rule should be displays in left-to-right layout - * direction, either always or with the current configuration. + * Computes the boundary position between the primary and the secondary containers for the given + * {@link ContainerPosition} with {@link SplitAttributes}, current window and device states. + * <ol> + * <li>For {@link #CONTAINER_POSITION_TOP}, it computes the boundary with the bottom + * container, which is {@link Rect#bottom} of the top container bounds.</li> + * <li>For {@link #CONTAINER_POSITION_BOTTOM}, it computes the boundary with the top + * container, which is {@link Rect#top} of the bottom container bounds.</li> + * <li>For {@link #CONTAINER_POSITION_LEFT}, it computes the boundary with the right + * container, which is {@link Rect#right} of the left container bounds.</li> + * <li>For {@link #CONTAINER_POSITION_RIGHT}, it computes the boundary with the bottom + * container, which is {@link Rect#left} of the right container bounds.</li> + * </ol> + * + * @see #getTopContainerBounds(Configuration, SplitAttributes, FoldingFeature) + * @see #getBottomContainerBounds(Configuration, SplitAttributes, FoldingFeature) + * @see #getLeftContainerBounds(Configuration, SplitAttributes, FoldingFeature) + * @see #getRightContainerBounds(Configuration, SplitAttributes, FoldingFeature) */ - private static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) { - switch (rule.getLayoutDirection()) { - case LayoutDirection.LOCALE: - return context.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_LTR; - case LayoutDirection.RTL: - return false; - case LayoutDirection.LTR: + private int computeBoundaryBetweenContainers(@NonNull Configuration taskConfiguration, + @NonNull SplitAttributes splitAttributes, @ContainerPosition int position, + @Nullable FoldingFeature foldingFeature) { + final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds(); + final int startPoint = shouldSplitHorizontally(splitAttributes) + ? parentBounds.top + : parentBounds.left; + final int dimen = shouldSplitHorizontally(splitAttributes) + ? parentBounds.height() + : parentBounds.width(); + final SplitType splitType = splitAttributes.getSplitType(); + if (splitType instanceof RatioSplitType) { + final RatioSplitType splitRatio = (RatioSplitType) splitType; + return (int) (startPoint + dimen * splitRatio.getRatio()); + } + // At this point, SplitType must be a HingeSplitType and foldingFeature must be + // non-null. RatioSplitType and ExpandContainerSplitType have been handled earlier. + Objects.requireNonNull(foldingFeature); + if (!(splitType instanceof HingeSplitType)) { + throw new IllegalArgumentException("Unknown splitType:" + splitType); + } + final Rect hingeArea = foldingFeature.getBounds(); + switch (position) { + case CONTAINER_POSITION_LEFT: + return hingeArea.left; + case CONTAINER_POSITION_TOP: + return hingeArea.top; + case CONTAINER_POSITION_RIGHT: + return hingeArea.right; + case CONTAINER_POSITION_BOTTOM: + return hingeArea.bottom; default: - return true; + throw new IllegalArgumentException("Unknown position:" + position); } } - @NonNull - static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { - return container.getTaskContainer().getTaskBounds(); + @Nullable + private FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) { + final int displayId = taskProperties.getDisplayId(); + final WindowConfiguration windowConfiguration = taskProperties.getConfiguration() + .windowConfiguration; + final WindowLayoutInfo info = mController.mWindowLayoutComponent + .getCurrentWindowLayoutInfo(displayId, windowConfiguration); + final List<DisplayFeature> displayFeatures = info.getDisplayFeatures(); + if (displayFeatures.isEmpty()) { + return null; + } + final List<FoldingFeature> foldingFeatures = new ArrayList<>(); + for (DisplayFeature displayFeature : displayFeatures) { + if (displayFeature instanceof FoldingFeature) { + foldingFeatures.add((FoldingFeature) displayFeature); + } + } + // TODO(b/240219484): Support device with multiple hinges. + if (foldingFeatures.size() != 1) { + return null; + } + return foldingFeatures.get(0); } - @NonNull - Rect getParentContainerBounds(@NonNull Activity activity) { - final TaskFragmentContainer container = mController.getContainerWithActivity(activity); - if (container != null) { - return getParentContainerBounds(container); + /** + * Indicates that this {@link SplitAttributes} splits the task horizontally. Returns + * {@code false} if this {@link SplitAttributes} splits the task vertically. + */ + private static boolean shouldSplitHorizontally(SplitAttributes splitAttributes) { + switch (splitAttributes.getLayoutDirection()) { + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: + return true; + default: + return false; } - // Obtain bounds from Activity instead because the Activity hasn't been embedded yet. - return getNonEmbeddedActivityBounds(activity); } /** - * Obtains the bounds from a non-embedded Activity. - * <p> - * Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most - * cases unless we want to obtain task bounds before - * {@link TaskContainer#isTaskBoundsInitialized()}. + * Computes the {@link SplitType} with the {@link SplitAttributes} and the current device and + * window state. + * If passed {@link SplitAttributes#getSplitType} is a {@link RatioSplitType}. It reversed + * the ratio if the computed {@link SplitAttributes#getLayoutDirection} is + * {@link SplitAttributes.LayoutDirection.LEFT_TO_RIGHT} or + * {@link SplitAttributes.LayoutDirection.BOTTOM_TO_TOP} to make the bounds calculation easier. + * If passed {@link SplitAttributes#getSplitType} is a {@link HingeSplitType}, it checks + * the current device and window states to determine whether the split container should split + * by hinge or use {@link HingeSplitType#getFallbackSplitType}. */ + private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes, + @NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) { + final int layoutDirection = splitAttributes.getLayoutDirection(); + final SplitType splitType = splitAttributes.getSplitType(); + if (splitType instanceof ExpandContainersSplitType) { + return splitType; + } else if (splitType instanceof RatioSplitType) { + final RatioSplitType splitRatio = (RatioSplitType) splitType; + // Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary + // computation have the same direction, which is from (top, left) to (bottom, right). + final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio()); + switch (layoutDirection) { + case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: + case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: + return splitType; + case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: + case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: + return reversedSplitType; + case LayoutDirection.LOCALE: { + boolean isLtr = taskConfiguration.getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR; + return isLtr ? splitType : reversedSplitType; + } + } + } else if (splitType instanceof HingeSplitType) { + final HingeSplitType hinge = (HingeSplitType) splitType; + @WindowingMode + final int windowingMode = taskConfiguration.windowConfiguration.getWindowingMode(); + return shouldSplitByHinge(splitAttributes, foldingFeature, windowingMode) + ? hinge : hinge.getFallbackSplitType(); + } + throw new IllegalArgumentException("Unknown SplitType:" + splitType); + } + + private static boolean shouldSplitByHinge(@NonNull SplitAttributes splitAttributes, + @Nullable FoldingFeature foldingFeature, @WindowingMode int taskWindowingMode) { + // Only HingeSplitType may split the task bounds by hinge. + if (!(splitAttributes.getSplitType() instanceof HingeSplitType)) { + return false; + } + // Device is not foldable, so there's no hinge to match. + if (foldingFeature == null) { + return false; + } + // The task is in multi-window mode. Match hinge doesn't make sense because current task + // bounds may not fit display bounds. + if (WindowConfiguration.inMultiWindowMode(taskWindowingMode)) { + return false; + } + // Return true if how the split attributes split the task bounds matches the orientation of + // folding area orientation. + return shouldSplitHorizontally(splitAttributes) == isFoldingAreaHorizontal(foldingFeature); + } + + private static boolean isFoldingAreaHorizontal(@NonNull FoldingFeature foldingFeature) { + final Rect bounds = foldingFeature.getBounds(); + return bounds.width() > bounds.height(); + } + + @NonNull + static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) { + return container.getTaskContainer().getTaskProperties(); + } + + @NonNull + TaskProperties getTaskProperties(@NonNull Activity activity) { + final TaskContainer taskContainer = mController.getTaskContainer( + mController.getTaskId(activity)); + if (taskContainer != null) { + return taskContainer.getTaskProperties(); + } + // Use a copy of configuration because activity's configuration may be updated later, + // or we may get unexpected TaskContainer's configuration if Activity's configuration is + // updated. An example is Activity is going to be in split. + return new TaskProperties(activity.getDisplayId(), + new Configuration(activity.getResources().getConfiguration())); + } + + @NonNull + WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) { + return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration()); + } + + @NonNull + private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) { + final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); + // TODO(b/190433398): Supply correct insets. + return new WindowMetrics(taskBounds, WindowInsets.CONSUMED); + } + + /** Obtains the bounds from a non-embedded Activity. */ @NonNull static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) { final WindowConfiguration windowConfiguration = diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index b5636777568e..91573ffef568 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -24,10 +24,13 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.Activity; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentParentInfo; +import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -42,13 +45,10 @@ class TaskContainer { /** The unique task id. */ private final int mTaskId; + // TODO(b/240219484): consolidate to mConfiguration /** Available window bounds of this Task. */ private final Rect mTaskBounds = new Rect(); - /** Windowing mode of this Task. */ - @WindowingMode - private int mWindowingMode = WINDOWING_MODE_UNDEFINED; - /** Active TaskFragments in this Task. */ @NonNull final List<TaskFragmentContainer> mContainers = new ArrayList<>(); @@ -57,24 +57,56 @@ class TaskContainer { @NonNull final List<SplitContainer> mSplitContainers = new ArrayList<>(); + @NonNull + private final Configuration mConfiguration; + + private int mDisplayId; + + private boolean mIsVisible; + /** * TaskFragments that the organizer has requested to be closed. They should be removed when - * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event - * for them. + * the organizer receives + * {@link SplitController#onTaskFragmentVanished(WindowContainerTransaction, TaskFragmentInfo)} + * event for them. */ final Set<IBinder> mFinishedContainer = new ArraySet<>(); - TaskContainer(int taskId) { + /** + * The {@link TaskContainer} constructor + * + * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with + * {@code activityInTask}. + * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to + * initialize the {@link TaskContainer} properties. + * + */ + TaskContainer(int taskId, @NonNull Activity activityInTask) { if (taskId == INVALID_TASK_ID) { throw new IllegalArgumentException("Invalid Task id"); } mTaskId = taskId; + // Make a copy in case the activity's config is updated, and updates the TaskContainer's + // config unexpectedly. + mConfiguration = new Configuration(activityInTask.getResources().getConfiguration()); + mDisplayId = activityInTask.getDisplayId(); + // Note that it is always called when there's a new Activity is started, which implies + // the host task is visible. + mIsVisible = true; } int getTaskId() { return mTaskId; } + int getDisplayId() { + return mDisplayId; + } + + boolean isVisible() { + return mIsVisible; + } + @NonNull Rect getTaskBounds() { return mTaskBounds; @@ -94,13 +126,21 @@ class TaskContainer { return !mTaskBounds.isEmpty(); } - void setWindowingMode(int windowingMode) { - mWindowingMode = windowingMode; + @NonNull + Configuration getConfiguration() { + // Make a copy in case the config is updated unexpectedly. + return new Configuration(mConfiguration); + } + + @NonNull + TaskProperties getTaskProperties() { + return new TaskProperties(mDisplayId, mConfiguration); } - /** Whether the Task windowing mode has been initialized. */ - boolean isWindowingModeInitialized() { - return mWindowingMode != WINDOWING_MODE_UNDEFINED; + void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { + mConfiguration.setTo(info.getConfiguration()); + mDisplayId = info.getDisplayId(); + mIsVisible = info.isVisibleRequested(); } /** @@ -123,13 +163,20 @@ class TaskContainer { // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the // Task windowing mode if the Task is in multi window. // TODO we won't need this anymore after we migrate Freeform caption to WM Shell. - return WindowConfiguration.inMultiWindowMode(mWindowingMode) - ? mWindowingMode - : WINDOWING_MODE_MULTI_WINDOW; + return isInMultiWindow() ? getWindowingMode() : WINDOWING_MODE_MULTI_WINDOW; } boolean isInPictureInPicture() { - return mWindowingMode == WINDOWING_MODE_PINNED; + return getWindowingMode() == WINDOWING_MODE_PINNED; + } + + boolean isInMultiWindow() { + return WindowConfiguration.inMultiWindowMode(getWindowingMode()); + } + + @WindowingMode + private int getWindowingMode() { + return getConfiguration().windowConfiguration.getWindowingMode(); } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ @@ -173,4 +220,28 @@ class TaskContainer { int indexOf(@NonNull TaskFragmentContainer child) { return mContainers.indexOf(child); } + + /** + * A wrapper class which contains the display ID and {@link Configuration} of a + * {@link TaskContainer} + */ + static final class TaskProperties { + private final int mDisplayId; + @NonNull + private final Configuration mConfiguration; + + TaskProperties(int displayId, @NonNull Configuration configuration) { + mDisplayId = displayId; + mConfiguration = configuration; + } + + int getDisplayId() { + return mDisplayId; + } + + @NonNull + Configuration getConfiguration() { + return mConfiguration; + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index f24401f0cd53..c76f568e117f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -47,6 +47,7 @@ import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.util.DataProducer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -68,6 +69,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; + private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>(); + private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners = new ArrayMap<>(); @@ -80,6 +83,11 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } + /** Registers to listen to {@link CommonFoldingFeature} changes */ + public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) { + mFoldingFeatureProducer.addDataChangedCallback(consumer); + } + /** * Adds a listener interested in receiving updates to {@link WindowLayoutInfo} * @@ -186,6 +194,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { + mLastReportedFoldingFeatures.clear(); + mLastReportedFoldingFeatures.addAll(storedFeatures); for (Context context : getContextsListeningForLayoutChanges()) { // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context); @@ -207,6 +217,27 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } /** + * Gets the current {@link WindowLayoutInfo} computed with passed {@link WindowConfiguration}. + * + * @return current {@link WindowLayoutInfo} on the default display. Returns + * empty {@link WindowLayoutInfo} on secondary displays. + */ + @NonNull + public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId, + @NonNull WindowConfiguration windowConfiguration) { + return getWindowLayoutInfo(displayId, windowConfiguration, mLastReportedFoldingFeatures); + } + + /** @see #getWindowLayoutInfo(Context, List) */ + private WindowLayoutInfo getWindowLayoutInfo(int displayId, + @NonNull WindowConfiguration windowConfiguration, + List<CommonFoldingFeature> storedFeatures) { + List<DisplayFeature> displayFeatureList = getDisplayFeatures(displayId, windowConfiguration, + storedFeatures); + return new WindowLayoutInfo(displayFeatureList); + } + + /** * Translate from the {@link CommonFoldingFeature} to * {@link DisplayFeature} for a given {@link Activity}. If a * {@link CommonFoldingFeature} is not valid then it will be omitted. @@ -225,12 +256,23 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { */ private List<DisplayFeature> getDisplayFeatures( @NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) { - List<DisplayFeature> features = new ArrayList<>(); if (!shouldReportDisplayFeatures(context)) { + return Collections.emptyList(); + } + return getDisplayFeatures(context.getDisplayId(), + context.getResources().getConfiguration().windowConfiguration, + storedFeatures); + } + + /** @see #getDisplayFeatures(Context, List) */ + private List<DisplayFeature> getDisplayFeatures(int displayId, + @NonNull WindowConfiguration windowConfiguration, + List<CommonFoldingFeature> storedFeatures) { + List<DisplayFeature> features = new ArrayList<>(); + if (displayId != DEFAULT_DISPLAY) { return features; } - int displayId = context.getDisplay().getDisplayId(); for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { @@ -238,7 +280,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } Rect featureRect = baseFeature.getRect(); rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(context, featureRect); + transformToWindowSpaceRect(windowConfiguration, featureRect); if (!isZero(featureRect)) { // TODO(b/228641877): Remove guarding when fixed. @@ -263,6 +305,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { windowingMode = ActivityClient.getInstance().getTaskWindowingMode( context.getActivityToken()); } else { + // TODO(b/242674941): use task windowing mode for window context that associates with + // activity. windowingMode = context.getResources().getConfiguration().windowConfiguration .getWindowingMode(); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java index 31bf96313a95..9e2611f392a3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java @@ -21,6 +21,7 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; @@ -89,13 +90,21 @@ public final class ExtensionHelper { /** Transforms rectangle from absolute coordinate space to the window coordinate space. */ public static void transformToWindowSpaceRect(@NonNull @UiContext Context context, Rect inOutRect) { - Rect windowRect = getWindowBounds(context); - if (!Rect.intersects(inOutRect, windowRect)) { + transformToWindowSpaceRect(getWindowBounds(context), inOutRect); + } + + /** @see ExtensionHelper#transformToWindowSpaceRect(Context, Rect) */ + public static void transformToWindowSpaceRect(@NonNull WindowConfiguration windowConfiguration, + Rect inOutRect) { + transformToWindowSpaceRect(windowConfiguration.getBounds(), inOutRect); + } + + private static void transformToWindowSpaceRect(@NonNull Rect bounds, @NonNull Rect inOutRect) { + if (!inOutRect.intersect(bounds)) { inOutRect.setEmpty(); return; } - inOutRect.intersect(windowRect); - inOutRect.offset(-windowRect.left, -windowRect.top); + inOutRect.offset(-bounds.left, -bounds.top); } /** diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index effc1a3ef3ea..40f7a273980a 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -16,9 +16,12 @@ package androidx.window.extensions.embedding; +import static android.view.Display.DEFAULT_DISPLAY; + import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import android.annotation.NonNull; @@ -26,32 +29,68 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.util.Pair; import android.window.TaskFragmentInfo; import android.window.WindowContainerToken; +import androidx.window.extensions.embedding.SplitAttributes.SplitType; +import androidx.window.extensions.layout.DisplayFeature; +import androidx.window.extensions.layout.FoldingFeature; +import androidx.window.extensions.layout.WindowLayoutInfo; + +import java.util.ArrayList; import java.util.Collections; +import java.util.List; public class EmbeddingTestUtils { static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); static final int TASK_ID = 10; - static final float SPLIT_RATIO = 0.5f; + static final SplitType SPLIT_TYPE = SplitType.RatioSplitType.splitEqually(); + static final SplitAttributes SPLIT_ATTRIBUTES = new SplitAttributes.Builder().build(); + static final String TEST_TAG = "test"; /** Default finish behavior in Jetpack. */ static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER; static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS; + private static final float SPLIT_RATIO = 0.5f; private EmbeddingTestUtils() {} /** Gets the bounds of a TaskFragment that is in split. */ static Rect getSplitBounds(boolean isPrimary) { - final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO); + return getSplitBounds(isPrimary, false /* shouldSplitHorizontally */); + } + + /** Gets the bounds of a TaskFragment that is in split. */ + static Rect getSplitBounds(boolean isPrimary, boolean shouldSplitHorizontally) { + final int dimension = (int) ( + (shouldSplitHorizontally ? TASK_BOUNDS.height() : TASK_BOUNDS.width()) + * SPLIT_RATIO); + if (shouldSplitHorizontally) { + return isPrimary + ? new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.right, + TASK_BOUNDS.top + dimension) + : new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top + dimension, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom); + } return isPrimary - ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width, - TASK_BOUNDS.bottom) + ? new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.left + dimension, + TASK_BOUNDS.bottom) : new Rect( - TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right, + TASK_BOUNDS.left + dimension, + TASK_BOUNDS.top, + TASK_BOUNDS.right, TASK_BOUNDS.bottom); } @@ -69,10 +108,15 @@ public class EmbeddingTestUtils { activityPair -> false, targetPair::equals, w -> true) - .setSplitRatio(SPLIT_RATIO) + .setDefaultSplitAttributes( + new SplitAttributes.Builder() + .setSplitType(SPLIT_TYPE) + .build() + ) .setShouldClearTop(clearTop) .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setTag(TEST_TAG) .build(); } @@ -101,10 +145,15 @@ public class EmbeddingTestUtils { targetPair::equals, activityIntentPair -> false, w -> true) - .setSplitRatio(SPLIT_RATIO) + .setDefaultSplitAttributes( + new SplitAttributes.Builder() + .setSplitType(SPLIT_TYPE) + .build() + ) .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary) .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary) .setShouldClearTop(clearTop) + .setTag(TEST_TAG) .build(); } @@ -130,4 +179,29 @@ public class EmbeddingTestUtils { primaryBounds.width() + 1, primaryBounds.height() + 1); return aInfo; } + + static TaskContainer createTestTaskContainer() { + Resources resources = mock(Resources.class); + doReturn(new Configuration()).when(resources).getConfiguration(); + Activity activity = mock(Activity.class); + doReturn(resources).when(activity).getResources(); + doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); + + return new TaskContainer(TASK_ID, activity); + } + + static WindowLayoutInfo createWindowLayoutInfo() { + final FoldingFeature foldingFeature = new FoldingFeature( + new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5, + TASK_BOUNDS.right, + TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5 + ), + FoldingFeature.TYPE_HINGE, + FoldingFeature.STATE_HALF_OPENED); + final List<DisplayFeature> displayFeatures = new ArrayList<>(); + displayFeatures.add(foldingFeature); + return new WindowLayoutInfo(displayFeatures); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index 58a627bafa16..957a24873998 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -19,6 +19,7 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -26,12 +27,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Point; +import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; @@ -65,7 +68,10 @@ public class JetpackTaskFragmentOrganizerTest { private WindowContainerTransaction mTransaction; @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; + @Mock private SplitController mSplitController; + @Mock + private Handler mHandler; private JetpackTaskFragmentOrganizer mOrganizer; @Before @@ -73,9 +79,8 @@ public class JetpackTaskFragmentOrganizerTest { MockitoAnnotations.initMocks(this); mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback); mOrganizer.registerOrganizer(); - mSplitController = new SplitController(); spyOn(mOrganizer); - spyOn(mSplitController); + doReturn(mHandler).when(mSplitController).getHandler(); } @Test @@ -113,7 +118,7 @@ public class JetpackTaskFragmentOrganizerTest { @Test public void testExpandTaskFragment() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, new Intent(), taskContainer, mSplitController); final TaskFragmentInfo info = createMockInfo(container); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 58870a66feea..179696a063b1 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -19,6 +19,7 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_CANCELED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; @@ -27,15 +28,20 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_I import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; -import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -73,14 +79,19 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.view.WindowInsets; +import android.view.WindowMetrics; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; +import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; +import androidx.window.extensions.layout.WindowLayoutInfo; import org.junit.Before; import org.junit.Test; @@ -116,6 +127,8 @@ public class SplitControllerTest { private WindowContainerTransaction mTransaction; @Mock private Handler mHandler; + @Mock + private WindowLayoutComponentImpl mWindowLayoutComponent; private SplitController mSplitController; private SplitPresenter mSplitPresenter; @@ -123,7 +136,9 @@ public class SplitControllerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mSplitController = new SplitController(); + doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + mSplitController = new SplitController(mWindowLayoutComponent); mSplitPresenter = mSplitController.mPresenter; spyOn(mSplitController); spyOn(mSplitPresenter); @@ -138,7 +153,7 @@ public class SplitControllerTest { @Test public void testGetTopActiveContainer() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); // tf1 has no running activity so is not active. final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, new Intent(), taskContainer, mSplitController); @@ -198,6 +213,7 @@ public class SplitControllerTest { @Test public void testOnTaskFragmentAppearEmptyTimeout() { final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any()); mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf); verify(mSplitPresenter).cleanupContainer(mTransaction, tf, @@ -268,6 +284,8 @@ public class SplitControllerTest { final SplitContainer splitContainer = mock(SplitContainer.class); doReturn(tf).when(splitContainer).getPrimaryContainer(); doReturn(tf).when(splitContainer).getSecondaryContainer(); + doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer(); + doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule(); final List<SplitContainer> splitContainers = mSplitController.getTaskContainer(TASK_ID).mSplitContainers; splitContainers.add(splitContainer); @@ -298,7 +316,7 @@ public class SplitControllerTest { // Verify if the top active split is updated if both of its containers are not finished. doReturn(false).when(mSplitController) - .dismissPlaceholderIfNecessary(mTransaction, splitContainer); + .dismissPlaceholderIfNecessary(mTransaction, splitContainer); mSplitController.updateContainer(mTransaction, tf); @@ -308,7 +326,7 @@ public class SplitControllerTest { @Test public void testOnStartActivityResultError() { final Intent intent = new Intent(); - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, intent, taskContainer, mSplitController); final SplitController.ActivityStartMonitor monitor = @@ -608,7 +626,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), - placeholderRule, true /* isPlaceholder */); + placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); } @Test @@ -624,7 +642,7 @@ public class SplitControllerTest { assertFalse(result); verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), - anyBoolean()); + any(), anyBoolean()); } @Test @@ -641,7 +659,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), - placeholderRule, true /* isPlaceholder */); + placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); } @Test @@ -656,7 +674,7 @@ public class SplitControllerTest { assertFalse(result); verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), - anyBoolean()); + any(), anyBoolean()); } @Test @@ -674,7 +692,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), - placeholderRule, true /* isPlaceholder */); + placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); } @Test @@ -693,14 +711,15 @@ public class SplitControllerTest { primaryContainer, mActivity, secondaryContainer, - splitRule); + splitRule, + SPLIT_ATTRIBUTES); clearInvocations(mSplitController); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); - verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any()); + verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @Test @@ -720,7 +739,8 @@ public class SplitControllerTest { primaryContainer, mActivity, secondaryContainer, - splitRule); + splitRule, + SPLIT_ATTRIBUTES); final Activity launchedActivity = createMockActivity(); primaryContainer.addPendingAppearedActivity(launchedActivity); @@ -741,7 +761,7 @@ public class SplitControllerTest { assertTrue(result); verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); - verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any()); + verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @Test @@ -778,7 +798,8 @@ public class SplitControllerTest { primaryContainer, mActivity, secondaryContainer, - placeholderRule); + placeholderRule, + SPLIT_ATTRIBUTES); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -1038,15 +1059,16 @@ public class SplitControllerTest { @Test public void testOnTransactionReady_taskFragmentParentInfoChanged() { final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); - final Configuration taskConfig = new Configuration(); + final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY, + DEFAULT_DISPLAY, true); transaction.addChange(new TaskFragmentTransaction.Change( TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) .setTaskId(TASK_ID) - .setTaskConfiguration(taskConfig)); + .setTaskFragmentParentInfo(parentInfo)); mSplitController.onTransactionReady(transaction); verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID), - eq(taskConfig)); + eq(parentInfo)); verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), anyInt(), anyBoolean()); } @@ -1088,6 +1110,47 @@ public class SplitControllerTest { anyInt(), anyBoolean()); } + @Test + public void testHasSamePresentation() { + SplitPairRule splitRule1 = new SplitPairRule.Builder( + activityPair -> true, + activityIntentPair -> true, + windowMetrics -> true) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) + .build(); + SplitPairRule splitRule2 = new SplitPairRule.Builder( + activityPair -> true, + activityIntentPair -> true, + windowMetrics -> true) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) + .build(); + + assertTrue("Rules must have same presentation if tags are null and has same properties.", + SplitController.haveSamePresentation(splitRule1, splitRule2, + new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); + + splitRule2 = new SplitPairRule.Builder( + activityPair -> true, + activityIntentPair -> true, + windowMetrics -> true) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) + .setTag(TEST_TAG) + .build(); + + assertFalse("Rules must have different presentations if tags are not equal regardless" + + "of other properties", + SplitController.haveSamePresentation(splitRule1, splitRule2, + new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); + + + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { final Activity activity = mock(Activity.class); @@ -1097,6 +1160,7 @@ public class SplitControllerTest { doReturn(activity).when(mSplitController).getActivity(activityToken); doReturn(TASK_ID).when(activity).getTaskId(); doReturn(new ActivityInfo()).when(activity).getActivityInfo(); + doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); return activity; } @@ -1135,7 +1199,7 @@ public class SplitControllerTest { private void setupPlaceholderRule(@NonNull Activity primaryActivity) { final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT, primaryActivity::equals, i -> false, w -> true) - .setSplitRatio(SPLIT_RATIO) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) .build(); mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule)); } @@ -1188,7 +1252,8 @@ public class SplitControllerTest { primaryContainer, primaryContainer.getTopNonFinishingActivity(), secondaryContainer, - rule); + rule, + SPLIT_ATTRIBUTES); // We need to set those in case we are not respecting clear top. // TODO(b/231845476) we should always respect clearTop. diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 25f0e25eec75..6dae0a1086b3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -16,23 +16,28 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.view.Display.DEFAULT_DISPLAY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; +import static androidx.window.extensions.embedding.SplitPresenter.EXPAND_CONTAINERS_ATTRIBUTES; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED; -import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition; import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; -import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -66,6 +71,8 @@ import android.window.WindowContainerTransaction; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; +import androidx.window.extensions.layout.WindowLayoutInfo; import org.junit.Before; import org.junit.Test; @@ -73,6 +80,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; + /** * Test class for {@link SplitPresenter}. * @@ -94,13 +103,17 @@ public class SplitPresenterTest { private TaskFragmentInfo mTaskFragmentInfo; @Mock private WindowContainerTransaction mTransaction; + @Mock + private WindowLayoutComponentImpl mWindowLayoutComponent; private SplitController mController; private SplitPresenter mPresenter; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mController = new SplitController(); + doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + mController = new SplitController(mWindowLayoutComponent); mPresenter = mController.mPresenter; spyOn(mController); spyOn(mPresenter); @@ -162,59 +175,263 @@ public class SplitPresenterTest { @Test public void testShouldShowSideBySide() { - Activity secondaryActivity = createMockActivity(); - final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); + assertTrue(SplitPresenter.shouldShowSplit(SPLIT_ATTRIBUTES)); + + final SplitAttributes expandContainers = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType()) + .build(); - assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule)); + assertFalse(SplitPresenter.shouldShowSplit(expandContainers)); + } - // Set minDimensions of primary container to larger than primary bounds. - final Rect primaryBounds = getSplitBounds(true /* isPrimary */); - Pair<Size, Size> minDimensionsPair = new Pair<>( - new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); + @Test + public void testGetBoundsForPosition_expandContainers() { + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType()) + .build(); - assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); } @Test - public void testGetBoundsForPosition() { - Activity secondaryActivity = createMockActivity(); - final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); - final Rect primaryBounds = getSplitBounds(true /* isPrimary */); - final Rect secondaryBounds = getSplitBounds(false /* isPrimary */); + public void testGetBoundsForPosition_splitVertically() { + final Rect primaryBounds = getSplitBounds(true /* isPrimary */, + false /* splitHorizontally */); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */, + false /* splitHorizontally */); + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) + .build(); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.RIGHT_TO_LEFT) + .build(); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); assertEquals("Primary bounds must be reported.", primaryBounds, - getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule, - mActivity, null /* miniDimensionsPair */)); + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) + .build(); + // Layout direction should follow screen layout for SplitAttributes.LayoutDirection.LOCALE. + taskProperties.getConfiguration().screenLayout |= Configuration.SCREENLAYOUT_LAYOUTDIR_RTL; assertEquals("Secondary bounds must be reported.", secondaryBounds, - getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule, - mActivity, null /* miniDimensionsPair */)); + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + } + + @Test + public void testGetBoundsForPosition_splitHorizontally() { + final Rect primaryBounds = getSplitBounds(true /* isPrimary */, + true /* splitHorizontally */); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */, + true /* splitHorizontally */); + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM) + .build(); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + splitAttributes = new SplitAttributes.Builder() + .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually()) + .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) + .build(); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + } + + @Test + public void testGetBoundsForPosition_useHingeFallback() { + final Rect primaryBounds = getSplitBounds(true /* isPrimary */, + false /* splitHorizontally */); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */, + false /* splitHorizontally */); + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.HingeSplitType( + SplitAttributes.SplitType.RatioSplitType.splitEqually() + )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) + .build(); + + // There's no hinge on the device. Use fallback SplitType. + doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + + assertEquals("PrimaryBounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("SecondaryBounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + // Hinge is reported, but the host task is in multi-window mode. Still use fallback + // splitType. + doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + taskProperties.getConfiguration().windowConfiguration + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + + assertEquals("PrimaryBounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("SecondaryBounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + + // Hinge is reported, and the host task is in fullscreen, but layout direction doesn't match + // folding area orientation. Still use fallback splitType. + doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + taskProperties.getConfiguration().windowConfiguration + .setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertEquals("PrimaryBounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("SecondaryBounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + } + + @Test + public void testGetBoundsForPosition_fallbackToExpandContainers() { + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.HingeSplitType( + new SplitAttributes.SplitType.ExpandContainersSplitType() + )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) + .build(); + assertEquals("Task bounds must be reported.", new Rect(), - getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule, - mActivity, null /* miniDimensionsPair */)); + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); - Pair<Size, Size> minDimensionsPair = new Pair<>( - new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", + new Rect(), + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); + } - assertEquals("Fullscreen bounds must be reported because of min dimensions.", + @Test + public void testGetBoundsForPosition_useHingeSplitType() { + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType(new SplitAttributes.SplitType.HingeSplitType( + new SplitAttributes.SplitType.ExpandContainersSplitType() + )).setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM) + .build(); + final WindowLayoutInfo windowLayoutInfo = createWindowLayoutInfo(); + doReturn(windowLayoutInfo).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + final Rect hingeBounds = windowLayoutInfo.getDisplayFeatures().get(0).getBounds(); + final Rect primaryBounds = new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.right, + hingeBounds.top + ); + final Rect secondaryBounds = new Rect( + TASK_BOUNDS.left, + hingeBounds.bottom, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom + ); + + assertEquals("PrimaryBounds must be reported.", + primaryBounds, + mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes)); + + assertEquals("SecondaryBounds must be reported.", + secondaryBounds, + mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes)); + assertEquals("Task bounds must be reported.", new Rect(), - getBoundsForPosition(POSITION_START, TASK_BOUNDS, - splitRule, mActivity, minDimensionsPair)); + mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes)); } @Test public void testExpandSplitContainerIfNeeded() { - SplitContainer splitContainer = mock(SplitContainer.class); Activity secondaryActivity = createMockActivity(); SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID); - doReturn(splitRule).when(splitContainer).getSplitRule(); - doReturn(primaryTf).when(splitContainer).getPrimaryContainer(); - doReturn(secondaryTf).when(splitContainer).getSecondaryContainer(); + SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity, + secondaryTf, splitRule, SPLIT_ATTRIBUTES); assertThrows(IllegalArgumentException.class, () -> mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity, @@ -224,11 +441,13 @@ public class SplitPresenterTest { splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); verify(mPresenter, never()).expandTaskFragment(any(), any()); + splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES); doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo(); assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded( mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); + splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES); primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity)); secondaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(secondaryTf, secondaryActivity)); @@ -238,6 +457,7 @@ public class SplitPresenterTest { verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken()); verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken()); + splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES); clearInvocations(mPresenter); assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, @@ -256,6 +476,7 @@ public class SplitPresenterTest { final SplitPairRule rule = new SplitPairRule.Builder(pair -> pair.first == mActivity && pair.second == secondaryActivity, pair -> false, metrics -> true) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) .setShouldClearTop(false) .build(); @@ -268,6 +489,49 @@ public class SplitPresenterTest { assertTrue(secondaryTf.isAbove(primaryTf)); } + @Test + public void testComputeSplitAttributes() { + final SplitPairRule splitPairRule = new SplitPairRule.Builder( + activityPair -> true, + activityIntentPair -> true, + windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS)) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) + .build(); + final TaskContainer.TaskProperties taskProperties = getTaskProperty(); + + assertEquals(SPLIT_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties, + splitPairRule, null /* minDimensionsPair */)); + + final Pair<Size, Size> minDimensionsPair = new Pair<>( + new Size(TASK_BOUNDS.width(), TASK_BOUNDS.height()), null); + + assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties, + splitPairRule, minDimensionsPair)); + + taskProperties.getConfiguration().windowConfiguration.setBounds(new Rect( + TASK_BOUNDS.left + 1, TASK_BOUNDS.top + 1, TASK_BOUNDS.right + 1, + TASK_BOUNDS.bottom + 1)); + + assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties, + splitPairRule, null /* minDimensionsPair */)); + + final SplitAttributes splitAttributes = new SplitAttributes.Builder() + .setSplitType( + new SplitAttributes.SplitType.HingeSplitType( + SplitAttributes.SplitType.RatioSplitType.splitEqually() + ) + ).build(); + + mController.setSplitAttributesCalculator(params -> { + return splitAttributes; + }); + + assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties, + splitPairRule, null /* minDimensionsPair */)); + } + private Activity createMockActivity() { final Activity activity = mock(Activity.class); final Configuration activityConfig = new Configuration(); @@ -279,4 +543,10 @@ public class SplitPresenterTest { doReturn(mock(IBinder.class)).when(activity).getActivityToken(); return activity; } + + private static TaskContainer.TaskProperties getTaskProperty() { + final Configuration configuration = new Configuration(); + configuration.windowConfiguration.setBounds(TASK_BOUNDS); + return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index dd67e48ef353..af9c6ba5c162 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -21,9 +21,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; -import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -34,8 +35,10 @@ import static org.mockito.Mockito.mock; import android.app.Activity; import android.content.Intent; +import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.window.TaskFragmentParentInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -66,7 +69,7 @@ public class TaskContainerTest { @Test public void testIsTaskBoundsInitialized() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertFalse(taskContainer.isTaskBoundsInitialized()); @@ -77,7 +80,7 @@ public class TaskContainerTest { @Test public void testSetTaskBounds() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertFalse(taskContainer.setTaskBounds(new Rect())); @@ -87,30 +90,24 @@ public class TaskContainerTest { } @Test - public void testIsWindowingModeInitialized() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); - - assertFalse(taskContainer.isWindowingModeInitialized()); - - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - - assertTrue(taskContainer.isWindowingModeInitialized()); - } - - @Test public void testGetWindowingModeForSplitTaskFragment() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final Rect splitBounds = new Rect(0, 0, 500, 1000); + final Configuration configuration = new Configuration(); assertEquals(WINDOWING_MODE_MULTI_WINDOW, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */)); assertEquals(WINDOWING_MODE_MULTI_WINDOW, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); - taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM); + configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */)); assertEquals(WINDOWING_MODE_FREEFORM, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); @@ -123,22 +120,27 @@ public class TaskContainerTest { @Test public void testIsInPictureInPicture() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); + final Configuration configuration = new Configuration(); assertFalse(taskContainer.isInPictureInPicture()); - taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */)); assertFalse(taskContainer.isInPictureInPicture()); - taskContainer.setWindowingMode(WINDOWING_MODE_PINNED); + configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */)); assertTrue(taskContainer.isInPictureInPicture()); } @Test public void testIsEmpty() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertTrue(taskContainer.isEmpty()); @@ -155,7 +157,7 @@ public class TaskContainerTest { @Test public void testGetTopTaskFragmentContainer() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertNull(taskContainer.getTopTaskFragmentContainer()); final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */, @@ -169,7 +171,7 @@ public class TaskContainerTest { @Test public void testGetTopNonFinishingActivity() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); assertNull(taskContainer.getTopNonFinishingActivity()); final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 082774e048a9..73428a2dc800 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -16,8 +16,8 @@ package androidx.window.extensions.embedding; -import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -90,7 +90,7 @@ public class TaskFragmentContainerTest { @Test public void testNewContainer() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); // One of the activity and the intent must be non-null assertThrows(IllegalArgumentException.class, @@ -103,7 +103,7 @@ public class TaskFragmentContainerTest { @Test public void testFinish() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); doReturn(container).when(mController).getContainerWithActivity(mActivity); @@ -136,7 +136,7 @@ public class TaskFragmentContainerTest { @Test public void testFinish_notFinishActivityThatIsReparenting() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity); @@ -157,7 +157,7 @@ public class TaskFragmentContainerTest { @Test public void testSetInfo() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); // Pending activity should be cleared when it has appeared on server side. final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); @@ -185,7 +185,7 @@ public class TaskFragmentContainerTest { @Test public void testIsWaitingActivityAppear() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); @@ -207,7 +207,7 @@ public class TaskFragmentContainerTest { @Test public void testAppearEmptyTimeout() { doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any()); - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); @@ -247,7 +247,7 @@ public class TaskFragmentContainerTest { @Test public void testCollectNonFinishingActivities() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); List<Activity> activities = container.collectNonFinishingActivities(); @@ -275,7 +275,7 @@ public class TaskFragmentContainerTest { @Test public void testAddPendingActivity() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); container.addPendingAppearedActivity(mActivity); @@ -289,7 +289,7 @@ public class TaskFragmentContainerTest { @Test public void testIsAbove() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, @@ -301,7 +301,7 @@ public class TaskFragmentContainerTest { @Test public void testGetBottomMostActivity() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); container.addPendingAppearedActivity(mActivity); @@ -318,7 +318,7 @@ public class TaskFragmentContainerTest { @Test public void testOnActivityDestroyed() { - final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); container.addPendingAppearedActivity(mActivity); diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 1f96617a1ede..cb0f22f974ad 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -238,6 +238,7 @@ LIBANDROID { ASurfaceControl_createFromWindow; # introduced=29 ASurfaceControl_acquire; # introduced=31 ASurfaceControl_release; # introduced=29 + ASurfaceControl_fromSurfaceControl; # introduced=34 ASurfaceTexture_acquireANativeWindow; # introduced=28 ASurfaceTexture_attachToGLContext; # introduced=28 ASurfaceTexture_detachFromGLContext; # introduced=28 @@ -255,6 +256,7 @@ LIBANDROID { ASurfaceTransaction_apply; # introduced=29 ASurfaceTransaction_create; # introduced=29 ASurfaceTransaction_delete; # introduced=29 + ASurfaceTransaction_fromTransaction; # introduced=34 ASurfaceTransaction_reparent; # introduced=29 ASurfaceTransaction_setBuffer; # introduced=29 ASurfaceTransaction_setBufferAlpha; # introduced=29 diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 42f4406ce5e8..9e4d72671502 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -17,6 +17,8 @@ #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h> #include <android/native_window.h> #include <android/surface_control.h> +#include <android/surface_control_jni.h> +#include <android_runtime/android_view_SurfaceControl.h> #include <configstore/Utils.h> #include <gui/HdrMetadata.h> #include <gui/ISurfaceComposer.h> @@ -28,6 +30,8 @@ #include <ui/DynamicDisplayInfo.h> #include <utils/Timers.h> +#include <utility> + using namespace android::hardware::configstore; using namespace android::hardware::configstore::V1_0; using namespace android; @@ -134,6 +138,11 @@ void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) { SurfaceControl_release(surfaceControl); } +ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) { + return reinterpret_cast<ASurfaceControl*>( + android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj)); +} + struct ASurfaceControlStats { std::variant<int64_t, sp<Fence>> acquireTimeOrFence; sp<Fence> previousReleaseFence; @@ -190,6 +199,11 @@ void ASurfaceTransaction_delete(ASurfaceTransaction* aSurfaceTransaction) { delete transaction; } +ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) { + return reinterpret_cast<ASurfaceTransaction*>( + android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj)); +} + void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) { CHECK_NOT_NULL(aSurfaceTransaction); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 123c01b6e12f..79fb56602328 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -210,13 +210,15 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { LocalBluetoothLeBroadcast(Context context) { mExecutor = Executors.newSingleThreadExecutor(); - BluetoothAdapter.getDefaultAdapter(). - getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); mBuilder = new BluetoothLeAudioContentMetadata.Builder(); mContentResolver = context.getContentResolver(); Handler handler = new Handler(Looper.getMainLooper()); mSettingsObserver = new BroadcastSettingsObserver(handler); updateBroadcastInfoFromContentProvider(); + + // Before registering callback, the constructor should finish creating the all of variables. + BluetoothAdapter.getDefaultAdapter() + .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); } /** diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml new file mode 100644 index 000000000000..eb160de7f249 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="133" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="133" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="150" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml new file mode 100644 index 000000000000..de972a62d7f7 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0.3" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="167" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0.3" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="167" + android:valueFrom="0.3" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="433" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="0.3" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="0.3" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml new file mode 100644 index 000000000000..e33b264ad528 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0.3" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="250" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="167" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="0.3" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="167" + android:valueFrom="0.3" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="250" + android:propertyName="fillAlpha" + android:startOffset="417" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="850" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="0.3" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="0.3" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 2cc5ccdc3fa1..1e5c53de4446 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -24,6 +24,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; import android.animation.Animator; @@ -106,6 +107,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_password; + case PROMPT_REASON_TRUSTAGENT_EXPIRED: + return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 987164557a7a..5b223242670c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -330,6 +330,9 @@ public class KeyguardPatternViewController case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); break; + case PROMPT_REASON_TRUSTAGENT_EXPIRED: + mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; case PROMPT_REASON_NONE: break; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index c46e33d9fd53..0a91150e6c39 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -22,6 +22,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; import android.animation.Animator; @@ -123,6 +124,8 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_pin; + case PROMPT_REASON_TRUSTAGENT_EXPIRED: + return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index ac00e9453c97..9d0a8acf02b4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -61,6 +61,12 @@ public interface KeyguardSecurityView { int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7; /** + * Some auth is required because the trustagent expired either from timeout or manually by + * the user + */ + int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8; + + /** * Reset the view and prepare to take input. This should do things like clearing the * password or pattern and clear error messages. */ diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 2e13903814a5..67b683ec643a 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -455,7 +455,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } - boolean needToUpdateProviderViews = false; final String newUniqueId = mDisplayInfo.uniqueId; if (!Objects.equals(newUniqueId, mDisplayUniqueId)) { mDisplayUniqueId = newUniqueId; @@ -473,37 +472,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab setupDecorations(); return; } - - if (mScreenDecorHwcLayer != null) { - updateHwLayerRoundedCornerDrawable(); - updateHwLayerRoundedCornerExistAndSize(); - } - needToUpdateProviderViews = true; - } - - final float newRatio = getPhysicalPixelDisplaySizeRatio(); - if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) { - mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio); - if (mScreenDecorHwcLayer != null) { - updateHwLayerRoundedCornerExistAndSize(); - } - needToUpdateProviderViews = true; - } - - if (needToUpdateProviderViews) { - updateOverlayProviderViews(null); - } else { - updateOverlayProviderViews(new Integer[] { - mFaceScanningViewId, - R.id.display_cutout, - R.id.display_cutout_left, - R.id.display_cutout_right, - R.id.display_cutout_bottom, - }); - } - - if (mScreenDecorHwcLayer != null) { - mScreenDecorHwcLayer.onDisplayChanged(newUniqueId); } } }; @@ -1069,6 +1037,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) { mRotation = newRotation; mDisplayMode = newMod; + mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( + getPhysicalPixelDisplaySizeRatio()); if (mScreenDecorHwcLayer != null) { mScreenDecorHwcLayer.pendingConfigChange = false; mScreenDecorHwcLayer.updateRotation(mRotation); diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt index a25286438387..8b4aeefb6ed4 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt @@ -78,23 +78,18 @@ class RoundedCornerResDelegate( reloadMeasures() } - private fun reloadAll(newReloadToken: Int) { - if (reloadToken == newReloadToken) { - return - } - reloadToken = newReloadToken - reloadRes() - reloadMeasures() - } - fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) { if (displayUniqueId != newDisplayUniqueId) { displayUniqueId = newDisplayUniqueId newReloadToken ?.let { reloadToken = it } reloadRes() reloadMeasures() - } else { - newReloadToken?.let { reloadAll(it) } + } else if (newReloadToken != null) { + if (reloadToken == newReloadToken) { + return + } + reloadToken = newReloadToken + reloadMeasures() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 26db3ee4926f..8c4d17d072e2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -24,6 +24,7 @@ import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -802,6 +803,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } else if (trustAgentsEnabled && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; + } else if (trustAgentsEnabled + && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) { + return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 || mUpdateMonitor.isFingerprintLockedOut())) { return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index b6f6e933bf84..624def60276b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -51,8 +51,6 @@ import javax.inject.Inject; /** Quick settings tile: Hotspot **/ public class HotspotTile extends QSTileImpl<BooleanState> { - private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot); - private final HotspotController mHotspotController; private final DataSaverController mDataSaverController; @@ -129,9 +127,6 @@ public class HotspotTile extends QSTileImpl<BooleanState> { @Override protected void handleUpdateState(BooleanState state, Object arg) { final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING; - if (state.slash == null) { - state.slash = new SlashState(); - } final int numConnectedDevices; final boolean isTransient = transientEnabling || mHotspotController.isHotspotTransient(); @@ -150,13 +145,14 @@ public class HotspotTile extends QSTileImpl<BooleanState> { isDataSaverEnabled = mDataSaverController.isDataSaverEnabled(); } - state.icon = mEnabledStatic; state.label = mContext.getString(R.string.quick_settings_hotspot_label); state.isTransient = isTransient; - state.slash.isSlashed = !state.value && !state.isTransient; if (state.isTransient) { state.icon = ResourceIcon.get( - com.android.internal.R.drawable.ic_hotspot_transient_animation); + R.drawable.qs_hotspot_icon_search); + } else { + state.icon = ResourceIcon.get(state.value + ? R.drawable.qs_hotspot_icon_on : R.drawable.qs_hotspot_icon_off); } state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 1011a6d831e6..d7e86b6e2919 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2230,7 +2230,8 @@ public final class NotificationPanelViewController extends PanelViewController { if (cancel) { collapse(false /* delayed */, 1.0f /* speedUpFactor */); } else { - maybeVibrateOnOpening(); + // Window never will receive touch events that typically trigger haptic on open. + maybeVibrateOnOpening(false /* openingWithTouch */); fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */); } onTrackingStopped(false); diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java index c3f1e571ab87..b4ce95c434fc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java @@ -96,6 +96,7 @@ public abstract class PanelViewController { private float mMinExpandHeight; private boolean mPanelUpdateWhenAnimatorEnds; private final boolean mVibrateOnOpening; + private boolean mHasVibratedOnOpen = false; protected boolean mIsLaunchAnimationRunning; private int mFixedDuration = NO_FIXED_DURATION; protected float mOverExpansion; @@ -353,8 +354,8 @@ public abstract class PanelViewController { private void startOpening(MotionEvent event) { updatePanelExpansionAndVisibility(); - maybeVibrateOnOpening(); - + // Reset at start so haptic can be triggered as soon as panel starts to open. + mHasVibratedOnOpen = false; //TODO: keyguard opens QS a different way; log that too? // Log the position of the swipe that opened the panel @@ -368,9 +369,18 @@ public abstract class PanelViewController { .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND); } - protected void maybeVibrateOnOpening() { + /** + * Maybe vibrate as panel is opened. + * + * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead + * being opened programmatically (such as by the open panel gesture), we always play haptic. + */ + protected void maybeVibrateOnOpening(boolean openingWithTouch) { if (mVibrateOnOpening) { - mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); + if (!openingWithTouch || !mHasVibratedOnOpen) { + mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); + mHasVibratedOnOpen = true; + } } } @@ -1371,6 +1381,9 @@ public abstract class PanelViewController { break; case MotionEvent.ACTION_MOVE: addMovement(event); + if (!isFullyCollapsed()) { + maybeVibrateOnOpening(true /* openingWithTouch */); + } float h = y - mInitialExpandY; // If the panel was collapsed when touching, we only need to check for the diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index df10dfe9f160..5a26d05d7b37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -1005,18 +1005,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize()); assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize()); - setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px) - /* roundedTopDrawable */, - getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px) - /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/); + doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio(); mDisplayInfo.rotation = Surface.ROTATION_270; mScreenDecorations.onConfigurationChanged(null); - assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize()); - assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize()); + assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize()); + assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize()); } @Test @@ -1293,51 +1288,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Test - public void testOnDisplayChanged_hwcLayer() { - setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); - decorationSupport.format = PixelFormat.R_8; - doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); - - // top cutout - mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); - - mScreenDecorations.start(); - - final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer; - spyOn(hwcLayer); - doReturn(mDisplay).when(hwcLayer).getDisplay(); - - mScreenDecorations.mDisplayListener.onDisplayChanged(1); - - verify(hwcLayer, times(1)).onDisplayChanged(any()); - } - - @Test - public void testOnDisplayChanged_nonHwcLayer() { - setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, - null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - - // top cutout - mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); - - mScreenDecorations.start(); - - final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView) - mScreenDecorations.getOverlayView(R.id.display_cutout); - assertNotNull(cutoutView); - spyOn(cutoutView); - doReturn(mDisplay).when(cutoutView).getDisplay(); - - mScreenDecorations.mDisplayListener.onDisplayChanged(1); - - verify(cutoutView, times(1)).onDisplayChanged(any()); - } - - @Test public void testHasSameProvidersWithNullOverlays() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt index f93336134900..93a1868b72f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt @@ -24,12 +24,11 @@ import androidx.annotation.DrawableRes import androidx.test.filters.SmallTest import com.android.internal.R as InternalR import com.android.systemui.R as SystemUIR -import com.android.systemui.tests.R import com.android.systemui.SysuiTestCase +import com.android.systemui.tests.R import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test - import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -102,14 +101,11 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) - setupResources(radius = 100, - roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px), - roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px)) - + roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f roundedCornerResDelegate.updateDisplayUniqueId(null, 1) - assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize) - assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize) + assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize) + assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java index b86713d0890b..451e9119f297 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.HotspotController; @@ -122,4 +123,40 @@ public class HotspotTileTest extends SysuiTestCase { .isEqualTo(mContext.getString(R.string.wifitrackerlib_admin_restricted_network)); mockitoSession.finishMocking(); } + + @Test + public void testIcon_whenDisabled_isOffState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + when(mHotspotController.isHotspotTransient()).thenReturn(false); + when(mHotspotController.isHotspotEnabled()).thenReturn(false); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_off)); + } + + @Test + public void testIcon_whenTransient_isSearchState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + when(mHotspotController.isHotspotTransient()).thenReturn(true); + when(mHotspotController.isHotspotEnabled()).thenReturn(true); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_search)); + } + + @Test + public void testIcon_whenEnabled_isOnState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + when(mHotspotController.isHotspotTransient()).thenReturn(false); + when(mHotspotController.isHotspotEnabled()).thenReturn(true); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_on)); + } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 676dc196f20c..ff154e463cb6 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -4185,8 +4185,6 @@ public class Vpn { */ @NonNull public synchronized List<String> getAppExclusionList(@NonNull String packageName) { - enforceNotRestrictedUser(); - final long oldId = Binder.clearCallingIdentity(); try { final byte[] bytes = getVpnProfileStore().get(getVpnAppExcludedForPackage(packageName)); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 58a80e3f977b..5a65afe4cf3b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -988,8 +988,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call com.android.internal.R.array.config_screenBrighteningThresholds); int[] screenDarkeningThresholds = resources.getIntArray( com.android.internal.R.array.config_screenDarkeningThresholds); - int[] screenThresholdLevels = resources.getIntArray( - com.android.internal.R.array.config_screenThresholdLevels); + float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources + .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels)); float screenDarkeningMinThreshold = mDisplayDeviceConfig.getScreenDarkeningMinThreshold(); float screenBrighteningMinThreshold = diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java index 34134892552f..abf8fe3d3f17 100644 --- a/services/core/java/com/android/server/display/HysteresisLevels.java +++ b/services/core/java/com/android/server/display/HysteresisLevels.java @@ -36,8 +36,7 @@ public class HysteresisLevels { private final float mMinBrightening; /** - * Creates a {@code HysteresisLevels} object with the given equal-length - * integer arrays. + * Creates a {@code HysteresisLevels} object for ambient brightness. * @param brighteningThresholds an array of brightening hysteresis constraint constants. * @param darkeningThresholds an array of darkening hysteresis constraint constants. * @param thresholdLevels a monotonically increasing array of threshold levels. @@ -59,6 +58,28 @@ public class HysteresisLevels { } /** + * Creates a {@code HysteresisLevels} object for screen brightness. + * @param brighteningThresholds an array of brightening hysteresis constraint constants. + * @param darkeningThresholds an array of darkening hysteresis constraint constants. + * @param thresholdLevels a monotonically increasing array of threshold levels. + * @param minBrighteningThreshold the minimum value for which the brightening value needs to + * return. + * @param minDarkeningThreshold the minimum value for which the darkening value needs to return. + */ + HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds, + float[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) { + if (brighteningThresholds.length != darkeningThresholds.length + || darkeningThresholds.length != thresholdLevels.length + 1) { + throw new IllegalArgumentException("Mismatch between hysteresis array lengths."); + } + mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f); + mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f); + mThresholdLevels = constraintInRangeIfNeeded(thresholdLevels); + mMinDarkening = minDarkeningThreshold; + mMinBrightening = minBrighteningThreshold; + } + + /** * Return the brightening hysteresis threshold for the given value level. */ public float getBrighteningThreshold(float value) { @@ -104,11 +125,42 @@ public class HysteresisLevels { private float[] setArrayFormat(int[] configArray, float divideFactor) { float[] levelArray = new float[configArray.length]; for (int index = 0; levelArray.length > index; ++index) { - levelArray[index] = (float)configArray[index] / divideFactor; + levelArray[index] = (float) configArray[index] / divideFactor; } return levelArray; } + /** + * This check is due to historical reasons, where screen thresholdLevels used to be + * integer values in the range of [0-255], but then was changed to be float values from [0,1]. + * To accommodate both the possibilities, we first check if all the thresholdLevels are in [0, + * 1], and if not, we divide all the levels with 255 to bring them down to the same scale. + */ + private float[] constraintInRangeIfNeeded(float[] thresholdLevels) { + if (isAllInRange(thresholdLevels, /* minValueInclusive = */ 0.0f, /* maxValueInclusive = */ + 1.0f)) { + return thresholdLevels; + } + + Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale"); + float[] thresholdLevelsScaled = new float[thresholdLevels.length]; + for (int index = 0; thresholdLevels.length > index; ++index) { + thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f; + } + return thresholdLevelsScaled; + } + + private boolean isAllInRange(float[] configArray, float minValueInclusive, + float maxValueInclusive) { + int configArraySize = configArray.length; + for (int index = 0; configArraySize > index; ++index) { + if (configArray[index] < minValueInclusive || configArray[index] > maxValueInclusive) { + return false; + } + } + return true; + } + void dump(PrintWriter pw) { pw.println("HysteresisLevels"); pw.println(" mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds)); diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 99950a0c185b..886e8e687e5a 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -687,7 +687,7 @@ public class TrustManagerService extends SystemService { */ public void lockUser(int userId) { mLockPatternUtils.requireStrongAuth( - StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId); + StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId); try { WindowManagerGlobal.getWindowManagerService().lockNow(null); } catch (RemoteException e) { @@ -2083,7 +2083,7 @@ public class TrustManagerService extends SystemService { if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) { if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout"); mLockPatternUtils.requireStrongAuth( - mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId); + mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId); } maybeLockScreen(mUserId); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 98d3adc71fe6..f1f68e1b10d4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1586,11 +1586,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (oldParent != null) { oldParent.cleanUpActivityReferences(this); + // Update isVisibleRequested value of parent TaskFragment and send the callback to the + // client side if needed. + oldParent.onActivityVisibleRequestedChanged(); } - if (newParent != null && isState(RESUMED)) { - newParent.setResumedActivity(this, "onParentChanged"); - mImeInsetsFrozenUntilStartInput = false; + if (newParent != null) { + // Update isVisibleRequested value of parent TaskFragment and send the callback to the + // client side if needed. + newParent.onActivityVisibleRequestedChanged(); + if (isState(RESUMED)) { + newParent.setResumedActivity(this, "onParentChanged"); + mImeInsetsFrozenUntilStartInput = false; + } } if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -5094,6 +5102,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } mVisibleRequested = visible; + final TaskFragment taskFragment = getTaskFragment(); + if (taskFragment != null) { + taskFragment.onActivityVisibleRequestedChanged(); + } setInsetsFrozen(!visible); if (app != null) { mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 837045c0019b..7251098d7d2b 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -186,6 +186,7 @@ import android.view.WindowManager.TransitionOldType; import android.window.ITaskOrganizer; import android.window.PictureInPictureSurfaceTransaction; import android.window.StartingWindowInfo; +import android.window.TaskFragmentParentInfo; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -2679,6 +2680,7 @@ class Task extends TaskFragment { if (isRootTask()) { updateSurfaceBounds(); } + sendTaskFragmentParentInfoChangedIfNeeded(); } boolean isResizeable() { @@ -3513,6 +3515,33 @@ class Task extends TaskFragment { return info; } + /** + * Returns the {@link TaskFragmentParentInfo} which will send to the client + * {@link android.window.TaskFragmentOrganizer} + */ + TaskFragmentParentInfo getTaskFragmentParentInfo() { + return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), isVisibleRequested()); + } + + @Override + void onActivityVisibleRequestedChanged() { + if (mVisibleRequested != isVisibleRequested()) { + sendTaskFragmentParentInfoChangedIfNeeded(); + } + } + + void sendTaskFragmentParentInfoChangedIfNeeded() { + if (!isLeafTask()) { + // Only send parent info changed event for leaf task. + return; + } + final TaskFragment childOrganizedTf = + getTaskFragment(TaskFragment::isOrganizedTaskFragment); + if (childOrganizedTf != null) { + childOrganizedTf.sendTaskFragmentParentInfoChanged(); + } + } + boolean isTaskId(int taskId) { return mTaskId == taskId; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index de4c84c27a69..2cfc563b0d6e 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -298,6 +298,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { final Point mLastSurfaceSize = new Point(); + /** The latest updated value when there's a child {@link #onActivityVisibleRequestedChanged} */ + boolean mVisibleRequested; + private final Rect mTmpBounds = new Rect(); private final Rect mTmpFullBounds = new Rect(); /** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */ @@ -2382,6 +2385,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { } } + void sendTaskFragmentParentInfoChanged() { + final Task parentTask = getParent().asTask(); + if (mTaskFragmentOrganizer != null && parentTask != null) { + mTaskFragmentOrganizerController + .onTaskFragmentParentInfoChanged(mTaskFragmentOrganizer, parentTask); + } + } + private void sendTaskFragmentAppeared() { if (mTaskFragmentOrganizer != null) { mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this); @@ -2417,7 +2428,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { mRemoteToken.toWindowContainerToken(), getConfiguration(), getNonFinishingActivityCount(), - isVisible(), + isVisibleRequested(), childActivities, positionInParent, mClearedTaskForReuse, @@ -2669,6 +2680,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds(); } + void onActivityVisibleRequestedChanged() { + final boolean isVisibleRequested = isVisibleRequested(); + if (mVisibleRequested == isVisibleRequested) { + return; + } + mVisibleRequested = isVisibleRequested; + final TaskFragment parentTf = getParent().asTaskFragment(); + if (parentTf != null) { + parentTf.onActivityVisibleRequestedChanged(); + } + } + String toFullString() { final StringBuilder sb = new StringBuilder(128); sb.append(this); diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 8c037a7390b1..2d5c9897a82c 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -49,6 +49,7 @@ import android.view.WindowManager; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -118,10 +119,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final Map<TaskFragment, Integer> mTaskFragmentTaskIds = new WeakHashMap<>(); /** - * Map from {@link Task#mTaskId} to the last Task {@link Configuration} sent to the + * Map from {@link Task#mTaskId} to the last {@link TaskFragmentParentInfo} sent to the * organizer. */ - private final SparseArray<Configuration> mLastSentTaskFragmentParentConfigs = + private final SparseArray<TaskFragmentParentInfo> mLastSentTaskFragmentParentInfos = new SparseArray<>(); /** @@ -225,7 +226,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr taskId = mTaskFragmentTaskIds.remove(tf); if (!mTaskFragmentTaskIds.containsValue(taskId)) { // No more TaskFragment in the Task. - mLastSentTaskFragmentParentConfigs.remove(taskId); + mLastSentTaskFragmentParentInfos.remove(taskId); } } else { // This can happen if the appeared wasn't sent before remove. @@ -260,25 +261,27 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Nullable - TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged( - @NonNull Task task) { + TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(@NonNull Task task) { final int taskId = task.mTaskId; // Check if the parent info is different from the last reported parent info. - final Configuration taskConfig = task.getConfiguration(); - final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(taskId); - if (configurationsAreEqualForOrganizer(taskConfig, lastParentConfig) - && taskConfig.windowConfiguration.getWindowingMode() - == lastParentConfig.windowConfiguration.getWindowingMode()) { + final TaskFragmentParentInfo parentInfo = task.getTaskFragmentParentInfo(); + final TaskFragmentParentInfo lastParentInfo = mLastSentTaskFragmentParentInfos + .get(taskId); + final Configuration lastParentConfig = lastParentInfo != null + ? lastParentInfo.getConfiguration() : null; + if (parentInfo.equalsForTaskFragmentOrganizer(lastParentInfo) + && configurationsAreEqualForOrganizer(parentInfo.getConfiguration(), + lastParentConfig)) { return null; } ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment parent info changed name=%s parentTaskId=%d", task.getName(), taskId); - mLastSentTaskFragmentParentConfigs.put(taskId, new Configuration(taskConfig)); + mLastSentTaskFragmentParentInfos.put(taskId, new TaskFragmentParentInfo(parentInfo)); return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) .setTaskId(taskId) - .setTaskConfiguration(taskConfig); + .setTaskFragmentParentInfo(parentInfo); } @NonNull @@ -646,6 +649,34 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .build()); } + void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, + @NonNull Task task) { + validateAndGetState(organizer); + final PendingTaskFragmentEvent pendingEvent = getLastPendingParentInfoChangedEvent( + organizer, task); + if (pendingEvent == null) { + addPendingEvent(new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer) + .setTask(task) + .build()); + } + } + + @Nullable + private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent( + @NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) { + final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents + .get(organizer.asBinder()); + for (int i = events.size() - 1; i >= 0; i--) { + final PendingTaskFragmentEvent event = events.get(i); + if (task == event.mTask + && event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) { + return event; + } + } + return null; + } + private void addPendingEvent(@NonNull PendingTaskFragmentEvent event) { mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).add(event); } @@ -848,7 +879,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) { - if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR) { + if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR + // Always send parent info changed to update task visibility + || event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) { return true; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8cc0d5dd8c1b..69d86b60302b 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2195,6 +2195,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound); } + @Nullable + TaskFragment getTaskFragment(Predicate<TaskFragment> callback) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final TaskFragment tf = mChildren.get(i).getTaskFragment(callback); + if (tf != null) { + return tf; + } + } + return null; + } + WindowState getWindow(Predicate<WindowState> callback) { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState w = mChildren.get(i).getWindow(callback); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 9bdf750767b3..61cf8cc76d83 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; import static android.window.TaskFragmentOrganizer.getTransitionType; @@ -76,6 +77,7 @@ import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizerToken; +import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -271,7 +273,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTaskFragmentParentInfoChanged() { setupMockParent(mTaskFragment, mTask); - mTask.getConfiguration().smallestScreenWidthDp = 10; + mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 10; mController.onTaskFragmentAppeared( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); @@ -295,7 +297,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Trigger callback if the size is changed. clearInvocations(mOrganizer); - mTask.getConfiguration().smallestScreenWidthDp = 100; + mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 100; mController.onTaskFragmentInfoChanged( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); @@ -304,7 +306,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Trigger callback if the windowing mode is changed. clearInvocations(mOrganizer); - mTask.getConfiguration().windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); + mTask.getTaskFragmentParentInfo().getConfiguration().windowConfiguration + .setWindowingMode(WINDOWING_MODE_PINNED); mController.onTaskFragmentInfoChanged( mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); @@ -1268,7 +1271,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final TaskFragmentTransaction.Change change = changes.get(0); assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType()); assertEquals(task.mTaskId, change.getTaskId()); - assertEquals(task.getConfiguration(), change.getTaskConfiguration()); + assertEquals(task.getTaskFragmentParentInfo(), change.getTaskFragmentParentInfo()); } /** Asserts that there will be a transaction for TaskFragment error. */ @@ -1316,8 +1319,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { /** Setups the mock Task as the parent of the given TaskFragment. */ private static void setupMockParent(TaskFragment taskFragment, Task mockParent) { doReturn(mockParent).when(taskFragment).getTask(); - final Configuration taskConfig = new Configuration(); - doReturn(taskConfig).when(mockParent).getConfiguration(); + doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true)) + .when(mockParent).getTaskFragmentParentInfo(); // Task needs to be visible mockParent.lastActiveTime = 100; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 12b4114930ca..b4244dd09bbd 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -191,9 +191,6 @@ public class TelephonyManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L; - // Null IMEI anomaly uuid - private static final UUID IMEI_ANOMALY_UUID = UUID.fromString( - "83905f14-6455-450c-be29-8206f0427fe9"); /** * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)} * into the ResultReceiver Bundle. @@ -2184,11 +2181,7 @@ public class TelephonyManager { @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM) public String getImei() { - String imei = getImei(getSlotIndex()); - if (imei == null) { - AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: IMEI is null."); - } - return imei; + return getImei(getSlotIndex()); } /** @@ -2231,10 +2224,7 @@ public class TelephonyManager { @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM) public String getImei(int slotIndex) { ITelephony telephony = getITelephony(); - if (telephony == null) { - AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: telephony is null"); - return null; - } + if (telephony == null) return null; try { return telephony.getImeiForSlot(slotIndex, getOpPackageName(), getAttributionTag()); |