diff options
18 files changed, 590 insertions, 53 deletions
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index 070b13b9e592..7cbb98e1bb4e 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -219,7 +219,8 @@ public final class MediaParser { * duration is unknown. */ public long getDurationMicros() { - return mExoPlayerSeekMap.getDurationUs(); + long durationUs = mExoPlayerSeekMap.getDurationUs(); + return durationUs != C.TIME_UNSET ? durationUs : UNKNOWN_DURATION; } /** diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java index 8bd36a516b12..8be5c63f31e3 100644 --- a/apex/statsd/framework/java/android/util/StatsEvent.java +++ b/apex/statsd/framework/java/android/util/StatsEvent.java @@ -26,6 +26,8 @@ import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.util.Arrays; + /** * StatsEvent builds and stores the buffer sent over the statsd socket. * This class defines and encapsulates the socket protocol. @@ -224,7 +226,9 @@ public final class StatsEvent { // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag. // See android_util_StatsLog.cpp. - private static final int MAX_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4; + private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4; + + private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB private final int mAtomId; private final byte[] mPayload; @@ -619,6 +623,7 @@ public final class StatsEvent { @NonNull public Builder usePooledBuffer() { mUsePooledBuffer = true; + mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos); return this; } @@ -694,8 +699,9 @@ public final class StatsEvent { @GuardedBy("sLock") private static Buffer sPool; - private final byte[] mBytes = new byte[MAX_PAYLOAD_SIZE]; + private byte[] mBytes = new byte[MAX_PUSH_PAYLOAD_SIZE]; private boolean mOverflow = false; + private int mMaxSize = MAX_PULL_PAYLOAD_SIZE; @NonNull private static Buffer obtain() { @@ -717,15 +723,26 @@ public final class StatsEvent { } private void release() { - synchronized (sLock) { - if (null == sPool) { - sPool = this; + // Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under. + if (mBytes.length <= MAX_PUSH_PAYLOAD_SIZE) { + synchronized (sLock) { + if (null == sPool) { + sPool = this; + } } } } private void reset() { mOverflow = false; + mMaxSize = MAX_PULL_PAYLOAD_SIZE; + } + + private void setMaxSize(final int maxSize, final int numBytesWritten) { + mMaxSize = maxSize; + if (numBytesWritten > maxSize) { + mOverflow = true; + } } private boolean hasOverflowed() { @@ -740,11 +757,28 @@ public final class StatsEvent { * @return true if space is available, false otherwise. **/ private boolean hasEnoughSpace(final int index, final int numBytes) { - final boolean result = index + numBytes < MAX_PAYLOAD_SIZE; - if (!result) { + final int totalBytesNeeded = index + numBytes; + + if (totalBytesNeeded > mMaxSize) { mOverflow = true; + return false; } - return result; + + // Expand buffer if needed. + if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) { + int newSize = mBytes.length; + do { + newSize *= 2; + } while (newSize <= totalBytesNeeded); + + if (newSize > mMaxSize) { + newSize = mMaxSize; + } + + mBytes = Arrays.copyOf(mBytes, newSize); + } + + return true; } /** diff --git a/apex/statsd/framework/test/src/android/util/StatsEventTest.java b/apex/statsd/framework/test/src/android/util/StatsEventTest.java index 7b511553a26f..8d263699d9c8 100644 --- a/apex/statsd/framework/test/src/android/util/StatsEventTest.java +++ b/apex/statsd/framework/test/src/android/util/StatsEventTest.java @@ -33,6 +33,7 @@ import org.junit.runner.RunWith; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Random; /** * Internal tests for {@link StatsEvent}. @@ -644,6 +645,165 @@ public class StatsEventTest { statsEvent.release(); } + @Test + public void testLargePulledEvent() { + final int expectedAtomId = 10_020; + byte[] field1 = new byte[10 * 1024]; + new Random().nextBytes(field1); + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = + StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()) + .isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()) + .isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("Third element is not byte array") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_BYTE_ARRAY); + + final byte[] field1Actual = getByteArrayFromByteBuffer(buffer); + assertWithMessage("Incorrect field 1").that(field1Actual).isEqualTo(field1); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testPulledEventOverflow() { + final int expectedAtomId = 10_020; + byte[] field1 = new byte[50 * 1024]; + new Random().nextBytes(field1); + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = + StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()) + .isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()) + .isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("Third element is not errors type") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_ERRORS); + + final int errorMask = buffer.getInt(); + + assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask") + .that(errorMask) + .isEqualTo(StatsEvent.ERROR_OVERFLOW); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testPushedEventOverflow() { + final int expectedAtomId = 10_020; + byte[] field1 = new byte[10 * 1024]; + new Random().nextBytes(field1); + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .writeByteArray(field1) + .usePooledBuffer() + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()) + .isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()) + .isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("Third element is not errors type") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_ERRORS); + + final int errorMask = buffer.getInt(); + + assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask") + .that(errorMask) + .isEqualTo(StatsEvent.ERROR_OVERFLOW); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) { final int numBytes = buffer.getInt(); byte[] bytes = new byte[numBytes]; diff --git a/core/res/res/values-television/themes_device_defaults.xml b/core/res/res/values-television/themes_device_defaults.xml index cb3d32815ea9..d6bdeee00a9f 100644 --- a/core/res/res/values-television/themes_device_defaults.xml +++ b/core/res/res/values-television/themes_device_defaults.xml @@ -33,5 +33,6 @@ <style name="Theme.DeviceDefault.Autofill.Light" parent="Theme.DeviceDefault.Autofill"/> <style name="Theme.DeviceDefault.Light.Autofill.Save" parent="Theme.DeviceDefault.Autofill.Save"/> - <style name="Theme.DeviceDefault.Resolver" parent="Theme.Leanback.Resolver" /> + <style name="Theme.DeviceDefault.ResolverCommon" parent="Theme.Leanback.Resolver" /> + <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon" /> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f3345b015c87..051bf7cca2d4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1785,6 +1785,7 @@ <java-symbol type="string" name="faceunlock_multiple_failures" /> <java-symbol type="string" name="global_actions" /> <java-symbol type="string" name="global_action_power_off" /> + <java-symbol type="string" name="global_action_power_options" /> <java-symbol type="string" name="global_action_restart" /> <java-symbol type="string" name="global_actions_airplane_mode_off_status" /> <java-symbol type="string" name="global_actions_airplane_mode_on_status" /> diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml index a80725c2758b..9dca9128e362 100644 --- a/core/res/res/values/themes_leanback.xml +++ b/core/res/res/values/themes_leanback.xml @@ -128,6 +128,10 @@ <!-- Toolbar attributes --> <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item> + + <!-- Icon sizes --> + <item name="iconfactoryIconSize">@dimen/resolver_icon_size</item> + <item name="iconfactoryBadgeSize">@dimen/resolver_badge_size</item> </style> <!-- @hide Special theme for the default system Activity-based Alert dialogs. --> diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java index 8ae98ae5e937..23fadac8a72b 100644 --- a/media/java/android/media/tv/TvRecordingClient.java +++ b/media/java/android/media/tv/TvRecordingClient.java @@ -146,6 +146,8 @@ public class TvRecordingClient { mPendingAppPrivateCommands.clear(); if (mSession != null) { mSession.release(); + mIsTuned = false; + mIsRecordingStarted = false; mSession = null; } } diff --git a/packages/SystemUI/res/layout/controls_detail_dialog.xml b/packages/SystemUI/res/layout/controls_detail_dialog.xml index d61122fd47dd..ee5315ad782f 100644 --- a/packages/SystemUI/res/layout/controls_detail_dialog.xml +++ b/packages/SystemUI/res/layout/controls_detail_dialog.xml @@ -42,6 +42,7 @@ android:layout_height="1dp" /> <ImageView android:id="@+id/control_detail_open_in_app" + android:contentDescription="@string/controls_open_app" android:src="@drawable/ic_open_in_new" android:background="?android:attr/selectableItemBackgroundBorderless" android:tint="@color/control_primary_text" diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 2ceb06690da6..91f361b15945 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -270,7 +270,7 @@ public class BubbleStackView extends FrameLayout private boolean mShowingDismiss = false; /** The view to desaturate/darken when magneted to the dismiss target. */ - private View mDesaturateAndDarkenTargetView; + @Nullable private View mDesaturateAndDarkenTargetView; private LayoutInflater mInflater; @@ -908,7 +908,10 @@ public class BubbleStackView extends FrameLayout // Update the paint and apply it to the bubble container. mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix)); - mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint); + + if (mDesaturateAndDarkenTargetView != null) { + mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint); + } }); // If the stack itself is touched, it means none of its touchable views (bubbles, flyouts, @@ -1894,6 +1897,10 @@ public class BubbleStackView extends FrameLayout private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) { mDesaturateAndDarkenTargetView = targetView; + if (mDesaturateAndDarkenTargetView == null) { + return; + } + if (desaturateAndDarken) { // Use the animated paint for the bubbles. mDesaturateAndDarkenTargetView.setLayerType( @@ -1915,9 +1922,14 @@ public class BubbleStackView extends FrameLayout } private void resetDesaturationAndDarken() { + mDesaturateAndDarkenAnimator.removeAllListeners(); mDesaturateAndDarkenAnimator.cancel(); - mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null); + + if (mDesaturateAndDarkenTargetView != null) { + mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null); + mDesaturateAndDarkenTargetView = null; + } } /** Animates in the dismiss target. */ diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 7e009b459ede..5e5ebe92e559 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -175,7 +175,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; private static final String GLOBAL_ACTION_KEY_USERS = "users"; private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; - private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; + static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; static final String GLOBAL_ACTION_KEY_RESTART = "restart"; @@ -213,7 +213,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @VisibleForTesting protected final ArrayList<Action> mItems = new ArrayList<>(); @VisibleForTesting - final ArrayList<Action> mOverflowItems = new ArrayList<>(); + protected final ArrayList<Action> mOverflowItems = new ArrayList<>(); + @VisibleForTesting + protected final ArrayList<Action> mPowerItems = new ArrayList<>(); @VisibleForTesting protected ActionsDialog mDialog; @@ -223,6 +225,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private MyAdapter mAdapter; private MyOverflowAdapter mOverflowAdapter; + private MyPowerOptionsAdapter mPowerAdapter; private boolean mKeyguardShowing = false; private boolean mDeviceProvisioned = false; @@ -584,14 +587,19 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mItems.clear(); mOverflowItems.clear(); - + mPowerItems.clear(); String[] defaultActions = getDefaultActions(); + + ShutDownAction shutdownAction = new ShutDownAction(); + RestartAction restartAction = new RestartAction(); + ArraySet<String> addedKeys = new ArraySet<String>(); + // make sure emergency affordance action is first, if needed if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { addActionItem(new EmergencyAffordanceAction()); + addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY); } - ArraySet<String> addedKeys = new ArraySet<String>(); for (int i = 0; i < defaultActions.length; i++) { String actionKey = defaultActions[i]; if (addedKeys.contains(actionKey)) { @@ -599,7 +607,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, continue; } if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { - addActionItem(new PowerAction()); + addActionItem(shutdownAction); } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { addActionItem(mAirplaneModeOn); } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { @@ -618,10 +626,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { addActionItem(getSettingsAction()); } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { - int userId = getCurrentUser().id; - if (Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) != 0 - && shouldDisplayLockdown(userId)) { + if (shouldDisplayLockdown(getCurrentUser())) { addActionItem(getLockdownAction()); } } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { @@ -629,7 +634,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { addActionItem(getAssistAction()); } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { - addActionItem(new RestartAction()); + addActionItem(restartAction); } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { addActionItem(new ScreenshotAction()); } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { @@ -638,15 +643,32 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, addActionItem(new LogoutAction()); } } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { - if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) { - addActionItem(new EmergencyDialerAction()); - } + addActionItem(new EmergencyDialerAction()); } else { Log.e(TAG, "Invalid global action key " + actionKey); } // Add here so we don't add more than one. addedKeys.add(actionKey); } + + // replace power and restart with a single power options action, if needed + if (mItems.contains(shutdownAction) && mItems.contains(restartAction) + && mOverflowItems.size() > 0) { + // transfer shutdown and restart to their own list of power actions + mItems.remove(shutdownAction); + mItems.remove(restartAction); + mPowerItems.add(shutdownAction); + mPowerItems.add(restartAction); + + // add the PowerOptionsAction after Emergency, if present + int powerIndex = addedKeys.contains(GLOBAL_ACTION_KEY_EMERGENCY) ? 1 : 0; + mItems.add(powerIndex, new PowerOptionsAction()); + + // transfer the first overflow action to the main set of items + Action firstOverflowAction = mOverflowItems.get(0); + mOverflowItems.remove(0); + mItems.add(firstOverflowAction); + } } private void onRotate() { @@ -664,6 +686,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mAdapter = new MyAdapter(); mOverflowAdapter = new MyOverflowAdapter(); + mPowerAdapter = new MyPowerOptionsAdapter(); mDepthController.setShowingHomeControls(true); GlobalActionsPanelPlugin.PanelViewController walletViewController = @@ -676,7 +699,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, walletViewController, mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, controlsAvailable(), uiController, - mSysUiState, this::onRotate, mKeyguardShowing); + mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter); boolean walletViewAvailable = walletViewController != null && walletViewController.getPanelContent() != null; if (shouldShowLockMessage(walletViewAvailable)) { @@ -689,7 +712,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return dialog; } - private boolean shouldDisplayLockdown(int userId) { + @VisibleForTesting + protected boolean shouldDisplayLockdown(UserInfo user) { + if (user == null) { + return false; + } + + int userId = user.id; + + // No lockdown option if it's not turned on in Settings + if (Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) == 0) { + return false; + } + // Lockdown is meaningless without a place to go. if (!mKeyguardStateController.isMethodSecure()) { return false; @@ -740,8 +776,32 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent); } - private final class PowerAction extends SinglePressAction implements LongPressAction { - private PowerAction() { + @VisibleForTesting + protected final class PowerOptionsAction extends SinglePressAction { + private PowerOptionsAction() { + super(R.drawable.ic_lock_power_off, R.string.global_action_power_options); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + if (mDialog != null) { + mDialog.showPowerOptionsMenu(); + } + } + } + + private final class ShutDownAction extends SinglePressAction implements LongPressAction { + private ShutDownAction() { super(R.drawable.ic_lock_power_off, R.string.global_action_power_off); } @@ -772,7 +832,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - private abstract class EmergencyAction extends SinglePressAction { + @VisibleForTesting + protected abstract class EmergencyAction extends SinglePressAction { EmergencyAction(int iconResId, int messageResId) { super(iconResId, messageResId); } @@ -1317,7 +1378,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Action item = mAdapter.getItem(position); if (!(item instanceof SilentModeTriStateAction)) { if (mDialog != null) { - mDialog.dismiss(); + // don't dismiss the dialog if we're opening the power options menu + if (!(item instanceof PowerOptionsAction)) { + mDialog.dismiss(); + } } else { Log.w(TAG, "Action clicked while mDialog is null."); } @@ -1334,6 +1398,70 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, /** * The adapter used for items in the overflow menu. */ + public class MyPowerOptionsAdapter extends BaseAdapter { + @Override + public int getCount() { + return mPowerItems.size(); + } + + @Override + public Action getItem(int position) { + return mPowerItems.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + if (action == null) { + Log.w(TAG, "No power options action found at position: " + position); + return null; + } + int viewLayoutResource = com.android.systemui.R.layout.controls_more_item; + View view = convertView != null ? convertView + : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); + TextView textView = (TextView) view; + if (action.getMessageResId() != 0) { + textView.setText(action.getMessageResId()); + } else { + textView.setText(action.getMessage()); + } + return textView; + } + + private boolean onLongClickItem(int position) { + final Action action = getItem(position); + if (action instanceof LongPressAction) { + if (mDialog != null) { + mDialog.dismiss(); + } else { + Log.w(TAG, "Action long-clicked while mDialog is null."); + } + return ((LongPressAction) action).onLongPress(); + } + return false; + } + + private void onClickItem(int position) { + Action item = getItem(position); + if (!(item instanceof SilentModeTriStateAction)) { + if (mDialog != null) { + mDialog.dismiss(); + } else { + Log.w(TAG, "Action clicked while mDialog is null."); + } + item.onPress(); + } + } + } + + /** + * The adapter used for items in the power options menu, triggered by the PowerOptionsAction. + */ public class MyOverflowAdapter extends BaseAdapter { @Override public int getCount() { @@ -1373,7 +1501,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, final Action action = getItem(position); if (action instanceof LongPressAction) { if (mDialog != null) { - mDialog.hidePowerOverflowMenu(); mDialog.dismiss(); } else { Log.w(TAG, "Action long-clicked while mDialog is null."); @@ -1387,7 +1514,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Action item = getItem(position); if (!(item instanceof SilentModeTriStateAction)) { if (mDialog != null) { - mDialog.hidePowerOverflowMenu(); mDialog.dismiss(); } else { Log.w(TAG, "Action clicked while mDialog is null."); @@ -1495,7 +1621,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - public int getMessageResId() { return mMessageResId; } @@ -1846,6 +1971,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off; mAirplaneModeOn.updateState(mAirplaneState); mAdapter.notifyDataSetChanged(); + mOverflowAdapter.notifyDataSetChanged(); + mPowerAdapter.notifyDataSetChanged(); } }; @@ -1928,6 +2055,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final Context mContext; private final MyAdapter mAdapter; private final MyOverflowAdapter mOverflowAdapter; + private final MyPowerOptionsAdapter mPowerOptionsAdapter; private final IStatusBarService mStatusBarService; private final IBinder mToken = new Binder(); private MultiListLayout mGlobalActionsLayout; @@ -1943,6 +2071,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final NotificationShadeDepthController mDepthController; private final SysUiState mSysUiState; private ListPopupWindow mOverflowPopup; + private ListPopupWindow mPowerOptionsPopup; private final Runnable mOnRotateCallback; private final boolean mControlsAvailable; @@ -1958,11 +2087,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, boolean controlsAvailable, @Nullable ControlsUiController controlsUiController, - SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing) { + SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, + MyPowerOptionsAdapter powerAdapter) { super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; mOverflowAdapter = overflowAdapter; + mPowerOptionsAdapter = powerAdapter; mDepthController = depthController; mColorExtractor = sysuiColorExtractor; mStatusBarService = statusBarService; @@ -2076,6 +2207,21 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + private ListPopupWindow createPowerOptionsPopup() { + GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu( + new ContextThemeWrapper( + mContext, + com.android.systemui.R.style.Control_ListPopupWindow + ), false /* isDropDownMode */); + popup.setOnItemClickListener( + (parent, view, position, id) -> mPowerOptionsAdapter.onClickItem(position)); + popup.setOnItemLongClickListener( + (parent, view, position, id) -> mPowerOptionsAdapter.onLongClickItem(position)); + popup.setAnchorView(mGlobalActionsLayout); + popup.setAdapter(mPowerOptionsAdapter); + return popup; + } + private ListPopupWindow createPowerOverflowPopup() { GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu( new ContextThemeWrapper( @@ -2093,16 +2239,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return popup; } + public void showPowerOptionsMenu() { + mPowerOptionsPopup = createPowerOptionsPopup(); + mPowerOptionsPopup.show(); + } + private void showPowerOverflowMenu() { mOverflowPopup = createPowerOverflowPopup(); mOverflowPopup.show(); } - private void hidePowerOverflowMenu() { - mOverflowPopup.dismiss(); - mOverflowPopup = null; - } - private void initializeLayout() { setContentView(com.android.systemui.R.layout.global_actions_grid_v2); fixNavBarClipping(); @@ -2278,6 +2424,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // close first, as popup windows will not fade during the animation dismissOverflow(false); + dismissPowerOptions(false); if (mControlsUiController != null) mControlsUiController.closeDialogs(false); }); } @@ -2302,6 +2449,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, resetOrientation(); dismissWallet(); dismissOverflow(true); + dismissPowerOptions(true); if (mControlsUiController != null) mControlsUiController.hide(); mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi); mDepthController.updateGlobalDialogVisibility(0, null /* view */); @@ -2326,6 +2474,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + private void dismissPowerOptions(boolean immediate) { + if (mPowerOptionsPopup != null) { + if (immediate) { + mPowerOptionsPopup.dismissImmediate(); + } else { + mPowerOptionsPopup.dismiss(); + } + } + } + private void setRotationSuggestionsEnabled(boolean enabled) { try { final int userId = Binder.getCallingUserHandle().getIdentifier(); @@ -2369,6 +2527,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // ensure dropdown menus are dismissed before re-initializing the dialog dismissWallet(); dismissOverflow(true); + dismissPowerOptions(true); if (mControlsUiController != null) { mControlsUiController.hide(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 4008918e267c..65d3572d04a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -355,10 +355,23 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } public void addTile(ComponentName tile) { + addTile(tile, /* end */ false); + } + + /** + * Adds a custom tile to the set of current tiles. + * @param tile the component name of the {@link android.service.quicksettings.TileService} + * @param end if true, the tile will be added at the end. If false, at the beginning. + */ + public void addTile(ComponentName tile, boolean end) { String spec = CustomTile.toSpec(tile); if (!mTileSpecs.contains(spec)) { List<String> newSpecs = new ArrayList<>(mTileSpecs); - newSpecs.add(0, spec); + if (end) { + newSpecs.add(spec); + } else { + newSpecs.add(0, spec); + } changeTiles(mTileSpecs, newSpecs); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt index b163818f68a8..93db9cdf85ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt @@ -341,7 +341,6 @@ class ChannelEditorDialogController @Inject constructor( } private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS - or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt index 88c325880241..c88f0bdc2acb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt @@ -184,7 +184,6 @@ class PriorityOnboardingDialogController @Inject constructor( } private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS - or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 79515415f1c3..fc8c8dbba7fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -274,7 +274,7 @@ public class AutoTileManager { } if (value != 0) { if (mSpec.startsWith(CustomTile.PREFIX)) { - mHost.addTile(CustomTile.getComponentFromSpec(mSpec)); + mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true); } else { mHost.addTile(mSpec); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index ce032e2ceaec..3455ff47de8d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -212,7 +212,6 @@ public class VolumeDialogImpl implements VolumeDialog, mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 32546333aac3..329af2b7f62b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -19,6 +19,7 @@ package com.android.systemui.globalactions; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -244,7 +245,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { } @Test - public void testCreateActionItems_maxThree() { + public void testCreateActionItems_maxThree_noOverflow() { mGlobalActionsDialog = spy(mGlobalActionsDialog); // allow 3 items to be shown doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); @@ -254,13 +255,129 @@ public class GlobalActionsDialogTest extends SysuiTestCase { GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertEquals(3, mGlobalActionsDialog.mItems.size()); + assertEquals(0, mGlobalActionsDialog.mOverflowItems.size()); + assertEquals(0, mGlobalActionsDialog.mPowerItems.size()); + } + + @Test + public void testCreateActionItems_maxThree_condensePower() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertEquals(3, mGlobalActionsDialog.mItems.size()); + assertEquals(0, mGlobalActionsDialog.mOverflowItems.size()); + assertEquals(2, mGlobalActionsDialog.mPowerItems.size()); + + // PowerOptionsAction should appear immediately after the Emergency action + + GlobalActionsDialog.Action firstItem = mGlobalActionsDialog.mItems.get(0); + GlobalActionsDialog.Action secondItem = mGlobalActionsDialog.mItems.get(1); + + assertTrue(firstItem instanceof GlobalActionsDialog.EmergencyAction); + assertTrue(secondItem instanceof GlobalActionsDialog.PowerOptionsAction); + } + + @Test + public void testCreateActionItems_maxThree_condensePower_noEmergency() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertEquals(3, mGlobalActionsDialog.mItems.size()); + assertEquals(0, mGlobalActionsDialog.mOverflowItems.size()); + assertEquals(2, mGlobalActionsDialog.mPowerItems.size()); + + // When Emergency isn't used, PowerOptionsAction should be first + + GlobalActionsDialog.Action firstItem = mGlobalActionsDialog.mItems.get(0); + GlobalActionsDialog.Action secondItem = mGlobalActionsDialog.mItems.get(1); + + assertTrue(firstItem instanceof GlobalActionsDialog.PowerOptionsAction); + assertTrue(secondItem instanceof GlobalActionsDialog.ScreenshotAction); + } + + @Test + public void testCreateActionItems_maxFour_condensePower() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(4).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertEquals(4, mGlobalActionsDialog.mItems.size()); + assertEquals(0, mGlobalActionsDialog.mOverflowItems.size()); + assertEquals(2, mGlobalActionsDialog.mPowerItems.size()); + + // with four items, make sure power still shows up immediately after Emergency + GlobalActionsDialog.Action firstItem = mGlobalActionsDialog.mItems.get(0); + GlobalActionsDialog.Action secondItem = mGlobalActionsDialog.mItems.get(1); + + assertTrue(firstItem instanceof GlobalActionsDialog.EmergencyAction); + assertTrue(secondItem instanceof GlobalActionsDialog.PowerOptionsAction); + } + + @Test + public void testCreateActionItems_maxThree_doNotCondensePower() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, }; doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); mGlobalActionsDialog.createActionItems(); assertEquals(3, mGlobalActionsDialog.mItems.size()); assertEquals(1, mGlobalActionsDialog.mOverflowItems.size()); + assertEquals(0, mGlobalActionsDialog.mPowerItems.size()); } @Test @@ -270,11 +387,13 @@ public class GlobalActionsDialogTest extends SysuiTestCase { doReturn(Integer.MAX_VALUE).when(mGlobalActionsDialog).getMaxShownPowerItems(); // ensure items are not blocked by keyguard or device provisioning doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); String[] actions = { GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, }; doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); mGlobalActionsDialog.createActionItems(); @@ -288,10 +407,12 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mGlobalActionsDialog = spy(mGlobalActionsDialog); // allow only 3 items to be shown doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // make sure lockdown action will NOT be shown + doReturn(false).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); String[] actions = { GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - // screenshot blocked because device not provisioned - GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT, + // lockdown action not allowed + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, }; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 11477395a781..5d4ef550b36c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -219,13 +219,43 @@ public class QSTileHostTest extends SysuiTestCase { public void testNoRepeatedSpecs_customTile() { mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, CUSTOM_TILE_SPEC); - mQSTileHost.addTile(CUSTOM_TILE); + mQSTileHost.addTile(CUSTOM_TILE, /* end */ false); assertEquals(1, mQSTileHost.mTileSpecs.size()); assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0)); } @Test + public void testAddedAtBeginningOnDefault_customTile() { + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed + + mQSTileHost.addTile(CUSTOM_TILE); + + assertEquals(2, mQSTileHost.mTileSpecs.size()); + assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0)); + } + + @Test + public void testAddedAtBeginning_customTile() { + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed + + mQSTileHost.addTile(CUSTOM_TILE, /* end */ false); + + assertEquals(2, mQSTileHost.mTileSpecs.size()); + assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0)); + } + + @Test + public void testAddedAtEnd_customTile() { + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed + + mQSTileHost.addTile(CUSTOM_TILE, /* end */ true); + + assertEquals(2, mQSTileHost.mTileSpecs.size()); + assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(1)); + } + + @Test public void testLoadTileSpec_repeated() { List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 1a6921a1d136..05cdd802167a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -172,11 +172,12 @@ public class AutoTileManagerTest extends SysuiTestCase { } @Test - public void testSettingTileAddedComponent_onChanged() { + public void testSettingTileAddedComponentAtEnd_onChanged() { changeValue(TEST_SETTING_COMPONENT, 1); waitForIdleSync(); verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC); - verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)); + verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT) + , /* end */ true); } @Test |