summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/AndroidManifest.xml1
-rw-r--r--api/Android.mk2
-rw-r--r--core/java/android/view/SurfaceControl.java1
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/window/TaskFragmentInfo.java4
-rw-r--r--core/java/android/window/TaskFragmentParentInfo.aidl23
-rw-r--r--core/java/android/window/TaskFragmentParentInfo.java158
-rw-r--r--core/java/android/window/TaskFragmentTransaction.java36
-rw-r--r--core/java/com/android/internal/app/SimpleIconFactory.java15
-rw-r--r--core/java/com/android/internal/os/Zygote.java16
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java9
-rw-r--r--core/jni/android_view_SurfaceControl.cpp51
-rw-r--r--core/jni/include/android_runtime/android_view_SurfaceControl.h38
-rw-r--r--core/res/res/values/config.xml9
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java29
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java271
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java545
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java103
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java50
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java17
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java88
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java11
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java105
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java332
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java48
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java24
-rw-r--r--native/android/libandroid.map.txt2
-rw-r--r--native/android/surface_control.cpp14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java6
-rw-r--r--packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml99
-rw-r--r--packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml110
-rw-r--r--packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml132
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java37
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java4
-rw-r--r--services/core/java/com/android/server/display/HysteresisLevels.java58
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java18
-rw-r--r--services/core/java/com/android/server/wm/Task.java29
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java25
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java59
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java15
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java14
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());