Merge "Add some logs to make current QS tiles state clearer" into tm-qpr-dev
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index d6586db..d9d7cc9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -185,6 +185,24 @@
public abstract String getId();
/**
+ * Get disabled reason of device
+ *
+ * @return disabled reason of device
+ */
+ public int getDisableReason() {
+ return -1;
+ }
+
+ /**
+ * Checks if device is has disabled reason
+ *
+ * @return true if device has disabled reason
+ */
+ public boolean hasDisabledReason() {
+ return false;
+ }
+
+ /**
* Checks if device is suggested device from application
*
* @return true if device is suggested device
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index 4e96dda..cfc38df 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -33,8 +33,8 @@
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
import androidx.compose.material3.contentColorFor
+import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
@@ -65,21 +65,17 @@
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.findRootCoordinates
-import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import com.android.compose.runtime.movableContentOf
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.max
import kotlin.math.min
-import kotlin.math.roundToInt
/**
* Create an expandable shape that can launch into an Activity or a Dialog.
@@ -220,21 +216,8 @@
// If this expandable is expanded when it's being directly clicked on, let's ensure that it has
// the minimum interactive size followed by all M3 components (48.dp).
val minInteractiveSizeModifier =
- if (onClick != null && LocalMinimumTouchTargetEnforcement.current) {
- // TODO(b/242040009): Replace this by Modifier.minimumInteractiveComponentSize() once
- // http://aosp/2305511 is available.
- val minTouchSize = LocalViewConfiguration.current.minimumTouchTargetSize
- Modifier.layout { measurable, constraints ->
- // Copied from androidx.compose.material3.InteractiveComponentSize.kt
- val placeable = measurable.measure(constraints)
- val width = maxOf(placeable.width, minTouchSize.width.roundToPx())
- val height = maxOf(placeable.height, minTouchSize.height.roundToPx())
- layout(width, height) {
- val centerX = ((width - placeable.width) / 2f).roundToInt()
- val centerY = ((height - placeable.height) / 2f).roundToInt()
- placeable.place(centerX, centerY)
- }
- }
+ if (onClick != null) {
+ Modifier.minimumInteractiveComponentSize()
} else {
Modifier
}
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 496eb6e..7e8bc2c 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -31,7 +31,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
app:layout_constraintEnd_toEndOf="@+id/actions_container"
- app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"/>
+ app:layout_constraintBottom_toTopOf="@id/guideline"/>
<HorizontalScrollView
android:id="@+id/actions_container"
android:layout_width="0dp"
@@ -127,57 +127,28 @@
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
android:elevation="7dp"/>
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_end="0dp" />
+
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/screenshot_message_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginVertical="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
- android:visibility="gone"
+ android:visibility="invisible"
+ app:layout_constraintTop_toBottomOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="parent">
-
- <ImageView
- android:id="@+id/screenshot_message_icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:paddingEnd="4dp"
- android:src="@drawable/ic_work_app_badge"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"/>
-
- <TextView
- android:id="@+id/screenshot_message_content"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
- app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
-
- <FrameLayout
- android:id="@+id/message_dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- android:contentDescription="@string/screenshot_dismiss_work_profile">
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:src="@drawable/overlay_cancel"/>
- </FrameLayout>
-
+ >
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
new file mode 100644
index 0000000..c794d91
--- /dev/null
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <ImageView
+ android:id="@+id/screenshot_message_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:paddingEnd="4dp"
+ android:src="@drawable/ic_work_app_badge"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+
+ <TextView
+ android:id="@+id/screenshot_message_content"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
+ app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
+
+ <FrameLayout
+ android:id="@+id/message_dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:contentDescription="@string/screenshot_dismiss_work_profile">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
+ </FrameLayout>
+</merge>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index dfc0150..f3bef92 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -337,7 +337,7 @@
<!-- Used for both start and bottom margin of the preview, relative to the action container -->
<dimen name="overlay_preview_container_margin">8dp</dimen>
<dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
- <dimen name="overlay_action_container_margin_bottom">4dp</dimen>
+ <dimen name="overlay_action_container_margin_bottom">6dp</dimen>
<dimen name="overlay_bg_protection_height">242dp</dimen>
<dimen name="overlay_action_container_corner_radius">18dp</dimen>
<dimen name="overlay_action_container_padding_vertical">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e60835c..5607c63 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2428,6 +2428,8 @@
<string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string>
<!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
<string name="media_output_group_title_suggested_device">Suggested Devices</string>
+ <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] -->
+ <string name="media_output_status_require_premium">Requires premium account</string>
<!-- Media Output Broadcast Dialog -->
<!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 1680b47..3a940e9 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -54,7 +54,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
@@ -309,15 +308,6 @@
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
}
- /**
- * Dump information for debugging
- */
- fun dump(pw: PrintWriter) {
- pw.println(this)
- clock?.dump(pw)
- regionSampler?.dump(pw)
- }
-
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
return scope.launch {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 7f1f941..8684019 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -45,6 +45,7 @@
import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.shared.regionsampling.RegionSampler;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -445,6 +446,10 @@
if (clock != null) {
clock.dump(pw);
}
+ final RegionSampler regionSampler = mClockEventController.getRegionSampler();
+ if (regionSampler != null) {
+ regionSampler.dump(pw);
+ }
}
/** Gets the animations for the current clock. */
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
index 18fb423..98896b1 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
@@ -37,9 +37,14 @@
private final int mId;
private String mOldClass;
- private ExtensionFragmentListener(View view, String tag, int id, Extension<T> extension) {
+ private ExtensionFragmentListener(
+ FragmentService fragmentService,
+ View view,
+ String tag,
+ int id,
+ Extension<T> extension) {
mTag = tag;
- mFragmentHostManager = FragmentHostManager.get(view);
+ mFragmentHostManager = fragmentService.getFragmentHostManager(view);
mExtension = extension;
mId = id;
mFragmentHostManager.getFragmentManager().beginTransaction()
@@ -61,8 +66,13 @@
mExtension.clearItem(true);
}
- public static <T> void attachExtensonToFragment(View view, String tag, int id,
+ public static <T> void attachExtensonToFragment(
+ FragmentService fragmentService,
+ View view,
+ String tag,
+ int id,
Extension<T> extension) {
- extension.addCallback(new ExtensionFragmentListener(view, tag, id, extension));
+ extension.addCallback(
+ new ExtensionFragmentListener(fragmentService, view, tag, id, extension));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 9c7411b..6a27ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -36,7 +36,6 @@
import androidx.annotation.NonNull;
import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.Dependency;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.util.leak.LeakDetector;
@@ -46,12 +45,17 @@
import java.util.ArrayList;
import java.util.HashMap;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
public class FragmentHostManager {
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Context mContext;
private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
private final View mRootView;
+ private final LeakDetector mLeakDetector;
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_ASSETS_PATHS);
@@ -61,14 +65,24 @@
private FragmentController mFragments;
private FragmentLifecycleCallbacks mLifecycleCallbacks;
- FragmentHostManager(FragmentService manager, View rootView) {
+ @AssistedInject
+ FragmentHostManager(
+ @Assisted View rootView,
+ FragmentService manager,
+ LeakDetector leakDetector) {
mContext = rootView.getContext();
mManager = manager;
mRootView = rootView;
+ mLeakDetector = leakDetector;
mConfigChanges.applyNewConfig(mContext.getResources());
createFragmentHost(null);
}
+ @AssistedFactory
+ public interface Factory {
+ FragmentHostManager create(View rootView);
+ }
+
private void createFragmentHost(Parcelable savedState) {
mFragments = FragmentController.createController(new HostCallbacks());
mFragments.attachHost(null);
@@ -86,7 +100,7 @@
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
- Dependency.get(LeakDetector.class).trackGarbage(f);
+ mLeakDetector.trackGarbage(f);
}
};
mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks,
@@ -211,19 +225,6 @@
}
}
- public static FragmentHostManager get(View view) {
- try {
- return Dependency.get(FragmentService.class).getFragmentHostManager(view);
- } catch (ClassCastException e) {
- // TODO: Some auto handling here?
- throw e;
- }
- }
-
- public static void removeAndDestroy(View view) {
- Dependency.get(FragmentService.class).removeAndDestroy(view);
- }
-
public void reloadFragments() {
Trace.beginSection("FrargmentHostManager#reloadFragments");
// Save the old state.
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index fe945fb..d302b13a 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -53,6 +53,7 @@
*/
private final ArrayMap<String, FragmentInstantiationInfo> mInjectionMap = new ArrayMap<>();
private final Handler mHandler = new Handler();
+ private final FragmentHostManager.Factory mFragmentHostManagerFactory;
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -67,8 +68,10 @@
@Inject
public FragmentService(
FragmentCreator.Factory fragmentCreatorFactory,
+ FragmentHostManager.Factory fragmentHostManagerFactory,
ConfigurationController configurationController,
DumpManager dumpManager) {
+ mFragmentHostManagerFactory = fragmentHostManagerFactory;
addFragmentInstantiationProvider(fragmentCreatorFactory.build());
configurationController.addCallback(mConfigurationListener);
@@ -152,7 +155,7 @@
public FragmentHostState(View view) {
mView = view;
- mFragmentHostManager = new FragmentHostManager(FragmentService.this, mView);
+ mFragmentHostManager = mFragmentHostManagerFactory.create(mView);
}
public void sendConfigurationChange(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 51b5a3d..769e0c8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,17 +16,21 @@
package com.android.systemui.media.dialog;
+import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import androidx.core.widget.CompoundButtonCompat;
import androidx.recyclerview.widget.RecyclerView;
@@ -186,6 +190,17 @@
mCurrentActivePosition = position;
updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(getItemTitle(device));
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+ && mController.isSubStatusSupported() && device.hasDisabledReason()) {
+ //update to subtext with device status
+ setUpDeviceIcon(device);
+ mSubTitleText.setText(
+ Api34Impl.composeDisabledReason(device.getDisableReason(), mContext));
+ updateConnectionFailedStatusIcon();
+ updateFullItemClickListener(null);
+ setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
+ false /* showProgressBar */, true /* showSubtitle */,
+ true /* showStatus */);
} else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
setUpDeviceIcon(device);
updateConnectionFailedStatusIcon();
@@ -389,4 +404,12 @@
mTitleText.setText(groupDividerTitle);
}
}
+
+ @RequiresApi(34)
+ private static class Api34Impl {
+ @DoNotInline
+ static String composeDisabledReason(int reason, Context context) {
+ return "";
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 4e08050..dc75538 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -45,6 +45,7 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settingslib.Utils;
@@ -142,11 +143,12 @@
final TextView mVolumeValueText;
final ImageView mTitleIcon;
final ProgressBar mProgressBar;
- final MediaOutputSeekbar mSeekBar;
final LinearLayout mTwoLineLayout;
final ImageView mStatusIcon;
final CheckBox mCheckBox;
final ViewGroup mEndTouchArea;
+ @VisibleForTesting
+ MediaOutputSeekbar mSeekBar;
private String mDeviceId;
private ValueAnimator mCornerAnimator;
private ValueAnimator mVolumeAnimator;
@@ -390,6 +392,7 @@
mTitleIcon.setVisibility(View.VISIBLE);
mVolumeValueText.setVisibility(View.GONE);
}
+ mController.logInteractionAdjustVolume(device);
mIsDragging = false;
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index f95da27..5f5c686 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -141,7 +141,7 @@
@VisibleForTesting
LocalMediaManager mLocalMediaManager;
@VisibleForTesting
- private MediaOutputMetricLogger mMetricLogger;
+ MediaOutputMetricLogger mMetricLogger;
private int mCurrentState;
private int mColorItemContent;
@@ -757,6 +757,10 @@
return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING);
}
+ public boolean isSubStatusSupported() {
+ return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_DEVICE_STATUS);
+ }
+
List<MediaDevice> getGroupMediaDevices() {
final List<MediaDevice> selectedDevices = getSelectedMediaDevice();
final List<MediaDevice> selectableDevices = getSelectableMediaDevice();
@@ -866,12 +870,15 @@
}
void adjustVolume(MediaDevice device, int volume) {
- mMetricLogger.logInteractionAdjustVolume(device);
ThreadUtils.postOnBackgroundThread(() -> {
device.requestSetVolume(volume);
});
}
+ void logInteractionAdjustVolume(MediaDevice device) {
+ mMetricLogger.logInteractionAdjustVolume(device);
+ }
+
String getPackageName() {
return mPackageName;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
new file mode 100644
index 0000000..1e531ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -0,0 +1,113 @@
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.drawable.Drawable
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import android.view.ViewTreeObserver
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.constraintlayout.widget.Guideline
+import com.android.systemui.R
+
+/**
+ * MessageContainerController controls the display of content in the screenshot message container.
+ */
+class MessageContainerController
+constructor(
+ parent: ViewGroup,
+) {
+ private val guideline: Guideline = parent.requireViewById(R.id.guideline)
+ private val messageContainer: ViewGroup =
+ parent.requireViewById(R.id.screenshot_message_container)
+
+ /**
+ * Show a notification under the screenshot view indicating that a work profile screenshot has
+ * been taken and which app can be used to view it.
+ *
+ * @param appName The name of the app to use to view screenshots
+ * @param appIcon Optional icon for the relevant files app
+ * @param onDismiss Runnable to be run when the user dismisses this message
+ */
+ fun showWorkProfileMessage(appName: CharSequence, appIcon: Drawable?, onDismiss: Runnable) {
+ // Eventually this container will support multiple notification types, but for now just make
+ // sure we don't double inflate.
+ if (messageContainer.childCount == 0) {
+ View.inflate(
+ messageContainer.context,
+ R.layout.screenshot_work_profile_first_run,
+ messageContainer
+ )
+ }
+ if (appIcon != null) {
+ // Replace the default icon if one is provided.
+ val imageView: ImageView =
+ messageContainer.requireViewById<ImageView>(R.id.screenshot_message_icon)
+ imageView.setImageDrawable(appIcon)
+ }
+ val messageContent =
+ messageContainer.requireViewById<TextView>(R.id.screenshot_message_content)
+ messageContent.text =
+ messageContainer.context.getString(
+ R.string.screenshot_work_profile_notification,
+ appName
+ )
+ messageContainer.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener {
+ animateOutMessageContainer()
+ onDismiss.run()
+ }
+
+ // Need the container to be fully measured before animating in (to know animation offset
+ // destination)
+ messageContainer.viewTreeObserver.addOnPreDrawListener(
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ messageContainer.viewTreeObserver.removeOnPreDrawListener(this)
+ animateInMessageContainer()
+ return false
+ }
+ }
+ )
+ }
+
+ private fun animateInMessageContainer() {
+ if (messageContainer.visibility == View.VISIBLE) return
+
+ messageContainer.visibility = View.VISIBLE
+ getAnimator(true).start()
+ }
+
+ private fun animateOutMessageContainer() {
+ getAnimator(false).apply {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ super.onAnimationEnd(animation)
+ messageContainer.visibility = View.INVISIBLE
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ private fun getAnimator(animateIn: Boolean): Animator {
+ val params = messageContainer.layoutParams as MarginLayoutParams
+ val offset = messageContainer.height + params.topMargin + params.bottomMargin
+ val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f)
+ with(anim) {
+ duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS
+ interpolator = AccelerateDecelerateInterpolator()
+ addUpdateListener { valueAnimator: ValueAnimator ->
+ val interpolation = valueAnimator.animatedValue as Float
+ guideline.setGuidelineEnd((interpolation * offset).toInt())
+ messageContainer.alpha = interpolation
+ }
+ }
+ return anim
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 9f7d4f0..ab13962 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -293,6 +293,7 @@
};
private ScreenshotView mScreenshotView;
+ private MessageContainerController mMessageContainerController;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
@@ -631,6 +632,7 @@
// Inflate the screenshot layout
mScreenshotView = (ScreenshotView)
LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+ mMessageContainerController = new MessageContainerController(mScreenshotView);
mScreenshotView.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
@@ -1193,7 +1195,8 @@
private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
mScreenshotView.setChipIntents(imageData);
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
- mWorkProfileMessageController.onScreenshotTaken(imageData.owner, mScreenshotView);
+ mWorkProfileMessageController.onScreenshotTaken(imageData.owner,
+ mMessageContainerController);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 8b73a57f..afba7ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -33,7 +33,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -82,7 +81,6 @@
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -102,8 +100,7 @@
* Handles the visual elements and animations for the screenshot flow.
*/
public class ScreenshotView extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener,
- WorkProfileMessageController.WorkProfileMessageDisplay {
+ ViewTreeObserver.OnComputeInternalInsetsListener {
interface ScreenshotViewCallback {
void onUserInteraction();
@@ -123,7 +120,7 @@
private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
- private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
+ public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
@@ -142,8 +139,6 @@
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
- private ViewGroup mMessageContainer;
- private TextView mMessageContent;
private ImageView mScreenshotPreview;
private ImageView mScreenshotBadge;
private View mScreenshotPreviewBorder;
@@ -295,8 +290,11 @@
mDismissButton.getBoundsOnScreen(tmpRect);
swipeRegion.op(tmpRect, Region.Op.UNION);
- mMessageContainer.findViewById(R.id.message_dismiss_button).getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
+ View messageDismiss = findViewById(R.id.message_dismiss_button);
+ if (messageDismiss != null) {
+ messageDismiss.getBoundsOnScreen(tmpRect);
+ swipeRegion.op(tmpRect, Region.Op.UNION);
+ }
return swipeRegion;
}
@@ -352,39 +350,11 @@
}
}
- /**
- * Show a notification under the screenshot view indicating that a work profile screenshot has
- * been taken and which app can be used to view it.
- *
- * @param appName The name of the app to use to view screenshots
- * @param appIcon Optional icon for the relevant files app
- * @param onDismiss Runnable to be run when the user dismisses this message
- */
- @Override
- public void showWorkProfileMessage(CharSequence appName, @Nullable Drawable appIcon,
- Runnable onDismiss) {
- if (appIcon != null) {
- // Replace the default icon if one is provided.
- ImageView imageView = mMessageContainer.findViewById(R.id.screenshot_message_icon);
- imageView.setImageDrawable(appIcon);
- }
- mMessageContent.setText(
- mContext.getString(R.string.screenshot_work_profile_notification, appName));
- mMessageContainer.setVisibility(VISIBLE);
- mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> {
- mMessageContainer.setVisibility(View.GONE);
- onDismiss.run();
- });
- }
-
@Override // View
protected void onFinishInflate() {
+ super.onFinishInflate();
mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
- mMessageContainer =
- requireNonNull(mScreenshotStatic.findViewById(R.id.screenshot_message_container));
- mMessageContent =
- requireNonNull(mMessageContainer.findViewById(R.id.screenshot_message_content));
mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
mScreenshotPreviewBorder = requireNonNull(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 5d7e56f..b4a07d4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -40,10 +40,10 @@
) {
/**
- * Determine if a message should be shown to the user, send message details to messageDisplay if
- * appropriate.
+ * Determine if a message should be shown to the user, send message details to
+ * MessageContainerController if appropriate.
*/
- fun onScreenshotTaken(userHandle: UserHandle, messageDisplay: WorkProfileMessageDisplay) {
+ fun onScreenshotTaken(userHandle: UserHandle, messageContainer: MessageContainerController) {
if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
var badgedIcon: Drawable? = null
var label: CharSequence? = null
@@ -65,7 +65,9 @@
val badgedLabel =
packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
- messageDisplay.showWorkProfileMessage(badgedLabel, badgedIcon) { onMessageDismissed() }
+ messageContainer.showWorkProfileMessage(badgedLabel, badgedIcon) {
+ onMessageDismissed()
+ }
}
}
@@ -89,15 +91,6 @@
private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
- /** UI that can show work profile messages (ScreenshotView in practice) */
- interface WorkProfileMessageDisplay {
- /**
- * Show the given message and icon, calling onDismiss if the user explicitly dismisses the
- * message.
- */
- fun showWorkProfileMessage(text: CharSequence, icon: Drawable?, onDismiss: Runnable)
- }
-
companion object {
const val TAG = "WorkProfileMessageCtrl"
const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 85b259e..de02115 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -31,6 +31,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
@@ -54,6 +55,7 @@
private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val featureFlags: FeatureFlags,
+ private val fragmentService: FragmentService,
@Main private val delayableExecutor: DelayableExecutor
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
@@ -128,6 +130,7 @@
mView.setInsetsChangedListener(delayedInsetSetter)
mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
mView.setConfigurationChangedListener { updateResources() }
+ fragmentService.getFragmentHostManager(mView).addTagListener(QS.TAG, mView)
}
override fun onViewDetached() {
@@ -136,6 +139,7 @@
mView.removeOnInsetsChangedListener()
mView.removeQSFragmentAttachedListener()
mView.setConfigurationChangedListener(null)
+ fragmentService.getFragmentHostManager(mView).removeTagListener(QS.TAG, mView)
}
fun updateResources() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 02316b7..f73dde6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -29,7 +29,6 @@
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.systemui.R;
-import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
@@ -133,18 +132,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- FragmentHostManager.get(this).addTagListener(QS.TAG, this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- FragmentHostManager.get(this).removeTagListener(QS.TAG, this);
- }
-
- @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mInsetsChangedListener.accept(insets);
return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5ff755e..85399ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1306,8 +1306,13 @@
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
- FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
- ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
+ FragmentHostManager fragmentHostManager =
+ mFragmentService.getFragmentHostManager(container);
+ ExtensionFragmentListener.attachExtensonToFragment(
+ mFragmentService,
+ container,
+ QS.TAG,
+ R.id.qs_frame,
mExtensionController
.newExtension(QS.class)
.withPlugin(QS.class)
@@ -1478,7 +1483,9 @@
}
protected QS createDefaultQSFragment() {
- return FragmentHostManager.get(mNotificationShadeWindowView).create(QSFragment.class);
+ return mFragmentService
+ .getFragmentHostManager(mNotificationShadeWindowView)
+ .create(QSFragment.class);
}
private void setUpPresenter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index e0d780a..7a4e35f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -48,6 +48,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
@@ -73,6 +74,7 @@
private boolean mIsAttached;
private final ViewGroup mStatusBarWindowView;
+ private final FragmentService mFragmentService;
// The container in which we should run launch animations started from the status bar and
// expanding into the opening window.
private final ViewGroup mLaunchAnimationContainer;
@@ -86,6 +88,7 @@
WindowManager windowManager,
IWindowManager iWindowManager,
StatusBarContentInsetsProvider contentInsetsProvider,
+ FragmentService fragmentService,
@Main Resources resources,
Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
mContext = context;
@@ -93,6 +96,7 @@
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
mStatusBarWindowView = statusBarWindowView;
+ mFragmentService = fragmentService;
mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
R.id.status_bar_launch_animation_container);
mLpChanged = new WindowManager.LayoutParams();
@@ -157,7 +161,7 @@
/** Returns a fragment host manager for the status bar window view. */
public FragmentHostManager getFragmentHostManager() {
- return FragmentHostManager.get(mStatusBarWindowView);
+ return mFragmentService.getFragmentHostManager(mStatusBarWindowView);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
index 79811c5..2475890 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
@@ -28,8 +28,9 @@
import androidx.preference.PreferenceScreen;
import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
import java.util.Objects;
@@ -74,7 +75,7 @@
RadioFragment f = new RadioFragment();
f.setPreference(this);
- FragmentHostManager.get(v).getFragmentManager()
+ Dependency.get(FragmentService.class).getFragmentHostManager(v).getFragmentManager()
.beginTransaction()
.add(android.R.id.content, f)
.commit();
@@ -86,8 +87,10 @@
Bundle savedInstanceState) {
super.onDialogStateRestored(fragment, dialog, savedInstanceState);
View view = dialog.findViewById(R.id.content);
- RadioFragment radioFragment = (RadioFragment) FragmentHostManager.get(view)
- .getFragmentManager().findFragmentById(R.id.content);
+ RadioFragment radioFragment = (RadioFragment) Dependency.get(FragmentService.class)
+ .getFragmentHostManager(view)
+ .getFragmentManager()
+ .findFragmentById(R.id.content);
if (radioFragment != null) {
radioFragment.setPreference(this);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 00b2fbe..b9c23d4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -15,7 +15,6 @@
*/
package com.android.keyguard
-import com.android.systemui.statusbar.CommandQueue
import android.content.BroadcastReceiver
import android.testing.AndroidTestingRunner
import android.view.View
@@ -34,6 +33,7 @@
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.any
@@ -41,8 +41,6 @@
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
-import java.util.TimeZone
-import java.util.concurrent.Executor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
@@ -57,8 +55,10 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import java.util.*
+import java.util.concurrent.Executor
+import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
index 77c837b..a2dc1eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
@@ -14,6 +14,7 @@
@SmallTest
class FragmentServiceTest : SysuiTestCase() {
private val fragmentCreator = TestFragmentCreator()
+ private val fragmenetHostManagerFactory: FragmentHostManager.Factory = mock()
private val fragmentCreatorFactory = FragmentService.FragmentCreator.Factory { fragmentCreator }
private lateinit var fragmentService: FragmentService
@@ -24,7 +25,13 @@
Looper.prepare()
}
- fragmentService = FragmentService(fragmentCreatorFactory, mock(), DumpManager())
+ fragmentService =
+ FragmentService(
+ fragmentCreatorFactory,
+ fragmenetHostManagerFactory,
+ mock(),
+ DumpManager()
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 7c3c9d2..8fd15c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -29,6 +29,7 @@
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.SeekBar;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
@@ -43,6 +44,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import java.util.ArrayList;
import java.util.List;
@@ -57,6 +60,7 @@
private static final String TEST_DEVICE_ID_1 = "test_device_id_1";
private static final String TEST_DEVICE_ID_2 = "test_device_id_2";
private static final String TEST_SESSION_NAME = "test_session_name";
+
private static final int TEST_MAX_VOLUME = 20;
private static final int TEST_CURRENT_VOLUME = 10;
@@ -69,6 +73,8 @@
private IconCompat mIconCompat = mock(IconCompat.class);
private View mDialogLaunchView = mock(View.class);
+ @Captor
+ private ArgumentCaptor<SeekBar.OnSeekBarChangeListener> mOnSeekBarChangeListenerCaptor;
private MediaOutputAdapter mMediaOutputAdapter;
private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
private List<MediaDevice> mMediaDevices = new ArrayList<>();
@@ -78,6 +84,7 @@
@Before
public void setUp() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
+ when(mMediaOutputController.isSubStatusSupported()).thenReturn(false);
when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems);
when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
@@ -348,6 +355,24 @@
}
@Test
+ public void onBindViewHolder_dragSeekbar_setsVolume() {
+ mOnSeekBarChangeListenerCaptor = ArgumentCaptor.forClass(
+ SeekBar.OnSeekBarChangeListener.class);
+ MediaOutputSeekbar mSpySeekbar = spy(mViewHolder.mSeekBar);
+ mViewHolder.mSeekBar = mSpySeekbar;
+ when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME);
+ when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_MAX_VOLUME);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mViewHolder.mSeekBar).setOnSeekBarChangeListener(
+ mOnSeekBarChangeListenerCaptor.capture());
+
+ mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1);
+ }
+
+ @Test
public void onBindViewHolder_bindSelectableDevice_verifyView() {
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
@@ -404,6 +429,24 @@
}
@Test
+ public void subStatusSupported_onBindViewHolder_bindFailedStateDevice_verifyView() {
+ String deviceStatus = "";
+ when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
+ when(mMediaDevice2.hasDisabledReason()).thenReturn(true);
+ when(mMediaDevice2.getDisableReason()).thenReturn(-1);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSubTitleText.getText()).isEqualTo(deviceStatus);
+ assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_2);
+ }
+
+ @Test
public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() {
when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice1.getState()).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 117751c..7c36e46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -93,6 +93,11 @@
private static final String TEST_SONG = "test_song";
private static final String TEST_SESSION_ID = "test_session_id";
private static final String TEST_SESSION_NAME = "test_session_name";
+ private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
+ ActivityLaunchAnimator.Controller.class);
+ private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
+ NearbyMediaDevicesManager.class);
// Mock
private MediaController mMediaController = mock(MediaController.class);
private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
@@ -111,12 +116,7 @@
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
- private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private FeatureFlags mFlags = mock(FeatureFlags.class);
- private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
- ActivityLaunchAnimator.Controller.class);
- private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
- NearbyMediaDevicesManager.class);
private View mDialogLaunchView = mock(View.class);
private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
@@ -522,6 +522,17 @@
}
@Test
+ public void logInteractionAdjustVolume_triggersFromMetricLogger() {
+ MediaOutputMetricLogger spyMediaOutputMetricLogger = spy(
+ mMediaOutputController.mMetricLogger);
+ mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger;
+
+ mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1);
+
+ verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1);
+ }
+
+ @Test
public void getSessionVolumeMax_triggersFromLocalMediaManager() {
mMediaOutputController.getSessionVolumeMax();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index bd04b3c..e8905ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -65,7 +65,7 @@
@Mock
private Context mContext;
@Mock
- private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay;
+ private MessageContainerController mMessageDisplay;
@Mock
private Drawable mActivityIcon;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index bdafc7d..c915502a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -13,8 +13,11 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.util.concurrency.FakeExecutor
@@ -29,6 +32,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.eq
@@ -69,6 +73,10 @@
private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
@Mock
private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var fragmentService: FragmentService
+ @Mock
+ private lateinit var fragmentHostManager: FragmentHostManager
@Captor
lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
@Captor
@@ -77,6 +85,8 @@
lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
@Captor
lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+ @Captor
+ lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
private lateinit var controller: NotificationsQSContainerController
private lateinit var navigationModeCallback: ModeChangedListener
@@ -91,8 +101,10 @@
mContext.ensureTestableResources()
whenever(notificationsQSContainer.context).thenReturn(mContext)
whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
+ whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
+
controller = NotificationsQSContainerController(
notificationsQSContainer,
navigationModeController,
@@ -100,6 +112,7 @@
largeScreenShadeHeaderController,
shadeExpansionStateManager,
featureFlags,
+ fragmentService,
delayableExecutor
)
@@ -114,9 +127,10 @@
doNothing().`when`(notificationsQSContainer)
.setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
-
+ doNothing().`when`(notificationsQSContainer)
+ .addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
controller.init()
- controller.onViewAttached()
+ attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
navigationModeCallback = navigationModeCaptor.value
taskbarVisibilityCallback = taskbarVisibilityCaptor.value
@@ -385,6 +399,7 @@
largeScreenShadeHeaderController,
shadeExpansionStateManager,
featureFlags,
+ fragmentService,
delayableExecutor
)
controller.updateConstraints()
@@ -426,6 +441,17 @@
verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L)
}
+ @Test
+ fun testTagListenerAdded() {
+ verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
+ }
+
+ @Test
+ fun testTagListenerRemoved() {
+ attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
+ verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
+ }
+
private fun disableSplitShade() {
setSplitShadeEnabled(false)
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 41721ce..2dad910 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -439,6 +439,9 @@
// finished destroying itself.
private static final int DESTROY_TIMEOUT = 10 * 1000;
+ // Rounding tolerance to be used in aspect ratio computations
+ private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
+
final ActivityTaskManagerService mAtmService;
@NonNull
final ActivityInfo info; // activity info provided by developer in AndroidManifest
@@ -8950,7 +8953,7 @@
int activityWidth = containingAppWidth;
int activityHeight = containingAppHeight;
- if (containingRatio > desiredAspectRatio) {
+ if (containingRatio - desiredAspectRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
if (containingAppWidth < containingAppHeight) {
// Width is the shorter side, so we use that to figure-out what the max. height
// should be given the aspect ratio.
@@ -8960,7 +8963,7 @@
// should be given the aspect ratio.
activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
}
- } else if (containingRatio < desiredAspectRatio) {
+ } else if (desiredAspectRatio - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
boolean adjustWidth;
switch (getRequestedConfigurationOrientation()) {
case ORIENTATION_LANDSCAPE:
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f8fd2b9..cf7d5d9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2007,7 +2007,8 @@
dc.getDisplayPolicy().simulateLayoutDisplay(df);
final InsetsState insetsState = df.mInsetsState;
final Rect displayFrame = insetsState.getDisplayFrame();
- final Insets decor = calculateDecorInsetsWithInternalTypes(insetsState);
+ final Insets decor = insetsState.calculateInsets(displayFrame, DECOR_TYPES,
+ true /* ignoreVisibility */);
final Insets statusBar = insetsState.calculateInsets(displayFrame,
Type.statusBars(), true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
@@ -2039,17 +2040,8 @@
}
}
- // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
- static final int[] INTERNAL_DECOR_TYPES;
- static {
- final ArraySet<Integer> decorTypes = InsetsState.toInternalType(
- Type.displayCutout() | Type.navigationBars());
- decorTypes.remove(ITYPE_EXTRA_NAVIGATION_BAR);
- INTERNAL_DECOR_TYPES = new int[decorTypes.size()];
- for (int i = 0; i < INTERNAL_DECOR_TYPES.length; i++) {
- INTERNAL_DECOR_TYPES[i] = decorTypes.valueAt(i);
- }
- }
+
+ static final int DECOR_TYPES = Type.displayCutout() | Type.navigationBars();
private final DisplayContent mDisplayContent;
private final Info[] mInfoForRotation = new Info[4];
@@ -2076,20 +2068,6 @@
info.mNeedUpdate = true;
}
}
-
- // TODO (b/235842600): Remove this method once we can treat task bar as navigation bar.
- private static Insets calculateDecorInsetsWithInternalTypes(InsetsState state) {
- final Rect frame = state.getDisplayFrame();
- Insets insets = Insets.NONE;
- for (int i = INTERNAL_DECOR_TYPES.length - 1; i >= 0; i--) {
- final InsetsSource source = state.peekSource(INTERNAL_DECOR_TYPES[i]);
- if (source != null) {
- insets = Insets.max(source.calculateInsets(frame, true /* ignoreVisibility */),
- insets);
- }
- }
- return insets;
- }
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 6f7ff5c..3404279 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -249,11 +249,13 @@
mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
- mRotation = readDefaultDisplayRotation(displayAddress);
+ int defaultRotation = readDefaultDisplayRotation(displayAddress);
+ mRotation = defaultRotation;
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
- mOrientationListener = new OrientationListener(mContext, uiHandler);
+ mOrientationListener =
+ new OrientationListener(mContext, uiHandler, defaultRotation);
mOrientationListener.setCurrentRotation(mRotation);
mSettingsObserver = new SettingsObserver(uiHandler);
mSettingsObserver.observe();
@@ -1735,8 +1737,9 @@
private class OrientationListener extends WindowOrientationListener implements Runnable {
transient boolean mEnabled;
- OrientationListener(Context context, Handler handler) {
- super(context, handler);
+ OrientationListener(Context context, Handler handler,
+ @Surface.Rotation int defaultRotation) {
+ super(context, handler, defaultRotation);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index 3e165e4..14c816d 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -88,14 +88,19 @@
private final Object mLock = new Object();
+ @Surface.Rotation
+ private final int mDefaultRotation;
+
/**
* Creates a new WindowOrientationListener.
*
* @param context for the WindowOrientationListener.
* @param handler Provides the Looper for receiving sensor updates.
+ * @param defaultRotation Default rotation of the display.
*/
- public WindowOrientationListener(Context context, Handler handler) {
- this(context, handler, SensorManager.SENSOR_DELAY_UI);
+ public WindowOrientationListener(Context context, Handler handler,
+ @Surface.Rotation int defaultRotation) {
+ this(context, handler, defaultRotation, SensorManager.SENSOR_DELAY_UI);
}
/**
@@ -103,7 +108,7 @@
*
* @param context for the WindowOrientationListener.
* @param handler Provides the Looper for receiving sensor updates.
- * @param wmService WindowManagerService to read the device config from.
+ * @param defaultRotation Default rotation of the display.
* @param rate at which sensor events are processed (see also
* {@link android.hardware.SensorManager SensorManager}). Use the default
* value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
@@ -111,10 +116,11 @@
*
* This constructor is private since no one uses it.
*/
- private WindowOrientationListener(
- Context context, Handler handler, int rate) {
+ private WindowOrientationListener(Context context, Handler handler,
+ @Surface.Rotation int defaultRotation, int rate) {
mContext = context;
mHandler = handler;
+ mDefaultRotation = defaultRotation;
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mRate = rate;
List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
@@ -1159,7 +1165,7 @@
"Reusing the last rotation resolution: " + mLastRotationResolution);
finalizeRotation(mLastRotationResolution);
} else {
- finalizeRotation(Surface.ROTATION_0);
+ finalizeRotation(mDefaultRotation);
}
return;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
index 3de65c1..1b3a199 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
@@ -132,6 +132,19 @@
assertThat(mFinalizedRotation).isEqualTo(DEFAULT_SENSOR_ROTATION);
}
+ @Test
+ public void testOnSensorChanged_screenLocked_doNotCallRotationResolverReturnDefaultRotation() {
+ mWindowOrientationListener = new TestableWindowOrientationListener(mMockContext,
+ mHandler, /* defaultRotation */ Surface.ROTATION_180);
+ mWindowOrientationListener.mRotationResolverService = mFakeRotationResolverInternal;
+ mWindowOrientationListener.mIsScreenLocked = true;
+
+ mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
+
+ assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse();
+ assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_180);
+ }
+
static final class TestableRotationResolver extends RotationResolverInternal {
@Surface.Rotation
RotationResolverCallbackInternal mCallback;
@@ -166,21 +179,17 @@
}
}
- @Test
- public void testOnSensorChanged_inLockScreen_doNotCallRotationResolver() {
- mWindowOrientationListener.mIsScreenLocked = true;
-
- mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
-
- assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse();
- }
-
final class TestableWindowOrientationListener extends WindowOrientationListener {
private boolean mIsOnProposedRotationChangedCalled = false;
private boolean mIsScreenLocked;
TestableWindowOrientationListener(Context context, Handler handler) {
- super(context, handler);
+ this(context, handler, Surface.ROTATION_0);
+ }
+
+ TestableWindowOrientationListener(Context context, Handler handler,
+ @Surface.Rotation int defaultRotation) {
+ super(context, handler, defaultRotation);
this.mOrientationJudge = new OrientationSensorJudge();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index a09bb33..c9ff150 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3055,6 +3055,20 @@
}
@Test
+ public void testApplyAspectRatio_containingRatioAlmostEqualToMaxRatio_boundsUnchanged() {
+ setUpDisplaySizeWithApp(1981, 2576);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+ final Rect originalBounds = new Rect(mActivity.getBounds());
+ prepareUnresizable(mActivity, 1.3f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // The containing aspect ratio is now 1.3003534, while the desired aspect ratio is 1.3. The
+ // bounds of the activity should not be changed as the difference is too small
+ assertEquals(mActivity.getBounds(), originalBounds);
+ }
+
+ @Test
public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
// When activity width equals parent width, multiplier shouldn't have any effect.
assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(