blob: ba49f3fa66ee68b043570829d1fe200b6340d097 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.qs.tileimpl;
import static androidx.lifecycle.Lifecycle.State.DESTROYED;
import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Intent;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class QSTileImplTest extends SysuiTestCase {
public static final int POSITION = 14;
private static final String SPEC = "spec";
@Mock
private QSLogger mQsLogger;
private TestableLooper mTestableLooper;
private TileImpl mTile;
@Mock
private QSTileHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
private final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private ActivityStarter mActivityStarter;
private UiEventLoggerFake mUiEventLoggerFake;
private InstanceId mInstanceId = InstanceId.fakeInstanceId(5);
@Captor
private ArgumentCaptor<LogMaker> mLogCaptor;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
mUiEventLoggerFake = new UiEventLoggerFake();
when(mHost.indexOf(SPEC)).thenReturn(POSITION);
when(mHost.getContext()).thenReturn(mContext);
when(mHost.getUiEventLogger()).thenReturn(mUiEventLoggerFake);
when(mHost.getNewInstanceId()).thenReturn(mInstanceId);
Handler mainHandler = new Handler(mTestableLooper.getLooper());
mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager,
mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger);
mTile.initialize();
mTestableLooper.processAllMessages();
mTile.setTileSpec(SPEC);
}
@Test
public void testClick_Metrics() {
mTile.click(null /* view */);
verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK)));
assertEquals(1, mUiEventLoggerFake.numLogs());
UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0);
assertEvent(QSEvent.QS_ACTION_CLICK, event);
}
@Test
public void testClick_log() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.click(null /* view */);
verify(mQsLogger).logTileClick(eq(SPEC), eq(StatusBarState.SHADE), eq(Tile.STATE_ACTIVE),
anyInt());
}
@Test
public void testHandleClick_log() {
mTile.click(null);
mTile.click(null);
mTestableLooper.processAllMessages();
mTile.click(null);
mTestableLooper.processAllMessages();
InOrder inOrder = inOrder(mQsLogger);
inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(0));
inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(1));
inOrder.verify(mQsLogger).logHandleClick(SPEC, 0);
inOrder.verify(mQsLogger).logHandleClick(SPEC, 1);
inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(2));
inOrder.verify(mQsLogger).logHandleClick(SPEC, 2);
}
@Test
public void testClick_Metrics_Status_Bar_Status() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.click(null /* view */);
verify(mMetricsLogger).write(mLogCaptor.capture());
assertEquals(StatusBarState.SHADE, mLogCaptor.getValue()
.getTaggedData(FIELD_STATUS_BAR_STATE));
}
@Test
public void testClick_falsing() {
mFalsingManager.setFalseTap(true);
mTile.click(null /* view */);
mTestableLooper.processAllMessages();
assertThat(mTile.mClicked).isFalse();
mFalsingManager.setFalseTap(false);
mTile.click(null /* view */);
mTestableLooper.processAllMessages();
assertThat(mTile.mClicked).isTrue();
}
@Test
public void testSecondaryClick_Metrics() {
mTile.secondaryClick(null /* view */);
verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
assertEquals(1, mUiEventLoggerFake.numLogs());
UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0);
assertEvent(QSEvent.QS_ACTION_SECONDARY_CLICK, event);
}
@Test
public void testSecondaryClick_log() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.secondaryClick(null /* view */);
verify(mQsLogger).logTileSecondaryClick(eq(SPEC), eq(StatusBarState.SHADE),
eq(Tile.STATE_ACTIVE), anyInt());
}
@Test
public void testHandleSecondaryClick_log() {
mTile.secondaryClick(null);
mTile.secondaryClick(null);
mTestableLooper.processAllMessages();
mTile.secondaryClick(null);
mTestableLooper.processAllMessages();
InOrder inOrder = inOrder(mQsLogger);
inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(0));
inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(1));
inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 0);
inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 1);
inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(2));
inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 2);
}
@Test
public void testSecondaryClick_Metrics_Status_Bar_Status() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mTile.secondaryClick(null /* view */);
verify(mMetricsLogger).write(mLogCaptor.capture());
assertEquals(StatusBarState.KEYGUARD, mLogCaptor.getValue()
.getTaggedData(FIELD_STATUS_BAR_STATE));
}
@Test
public void testLongClick_Metrics() {
mTile.longClick(null /* view */);
verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
assertEquals(1, mUiEventLoggerFake.numLogs());
UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0);
assertEvent(QSEvent.QS_ACTION_LONG_PRESS, event);
}
@Test
public void testLongClick_log() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.longClick(null /* view */);
verify(mQsLogger).logTileLongClick(eq(SPEC), eq(StatusBarState.SHADE),
eq(Tile.STATE_ACTIVE), anyInt());
}
@Test
public void testHandleLongClick_log() {
mTile.longClick(null);
mTile.longClick(null);
mTestableLooper.processAllMessages();
mTile.longClick(null);
mTestableLooper.processAllMessages();
InOrder inOrder = inOrder(mQsLogger);
inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(0));
inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(1));
inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 0);
inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 1);
inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(2));
inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 2);
}
@Test
public void testLongClick_Metrics_Status_Bar_Status() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
mTile.click(null /* view */);
verify(mMetricsLogger).write(mLogCaptor.capture());
assertEquals(StatusBarState.SHADE_LOCKED, mLogCaptor.getValue()
.getTaggedData(FIELD_STATUS_BAR_STATE));
}
@Test
public void testPopulateWithLockedScreen() {
LogMaker maker = mock(LogMaker.class);
when(maker.setSubtype(anyInt())).thenReturn(maker);
when(maker.addTaggedData(anyInt(), any())).thenReturn(maker);
mTile.getState().value = true;
mTile.populate(maker);
verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
}
@Test
public void testPopulateWithUnlockedScreen() {
LogMaker maker = mock(LogMaker.class);
when(maker.setSubtype(anyInt())).thenReturn(maker);
when(maker.addTaggedData(anyInt(), any())).thenReturn(maker);
mTile.getState().value = true;
mTile.populate(maker);
verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
}
//TODO(b/161799397) Bring back testStaleTimeout when we can use FakeExecutor
@Test
public void testStaleListening() {
mTile.handleStale();
mTestableLooper.processAllMessages();
verify(mQsLogger).logTileChangeListening(SPEC, true);
mTile.handleRefreshState(null);
mTestableLooper.processAllMessages();
verify(mQsLogger).logTileChangeListening(SPEC, false);
}
@Test
public void testHandleDestroyClearsHandlerQueue() {
mTile.handleRefreshState(null); // this will add a delayed H.STALE message
mTile.handleDestroy();
assertFalse(mTile.mHandler.hasMessages(QSTileImpl.H.STALE));
}
@Test
public void testHandleDestroyLifecycle() {
assertNotEquals(DESTROYED, mTile.getLifecycle().getCurrentState());
mTile.handleDestroy();
mTestableLooper.processAllMessages();
assertEquals(DESTROYED, mTile.getLifecycle().getCurrentState());
}
@Test
public void testHandleDestroy_log() {
mTile.handleDestroy();
verify(mQsLogger).logTileDestroyed(eq(SPEC), anyString());
}
@Test
public void testListening_log() {
mTile.handleSetListening(true);
verify(mQsLogger).logTileChangeListening(SPEC, true);
mTile.handleSetListening(false);
verify(mQsLogger).logTileChangeListening(SPEC, false);
}
@Test
public void testListeningTrue_stateAtLeastResumed() {
mTile.setListening(new Object(), true); // Listen with some object
TestableLooper.get(this).processAllMessages();
assertTrue(mTile.getLifecycle().getCurrentState().isAtLeast(RESUMED));
}
@Test
public void testTileDoesntStartResumed() {
assertFalse(mTile.getLifecycle().getCurrentState().isAtLeast(RESUMED));
}
@Test
public void testListeningFalse_stateAtMostCreated() {
Object o = new Object();
mTile.setListening(o, true);
mTile.setListening(o, false);
TestableLooper.get(this).processAllMessages();
assertFalse(mTile.getLifecycle().getCurrentState().isAtLeast(RESUMED));
}
@Test
public void testListeningFalse_stateNotDestroyed() {
Object o = new Object();
mTile.setListening(o, true);
mTile.setListening(o, false);
TestableLooper.get(this).processAllMessages();
assertNotEquals(DESTROYED, mTile.getLifecycle().getCurrentState());
}
@Test
public void testRefreshStateAfterDestroyedDoesNotCrash() {
mTile.destroy();
mTile.refreshState();
mTestableLooper.processAllMessages();
}
@Test
public void testSetListeningAfterDestroyedDoesNotCrash() {
Object o = new Object();
mTile.destroy();
mTile.setListening(o, true);
mTile.setListening(o, false);
mTestableLooper.processAllMessages();
}
@Test
public void testClickOnDisabledByPolicyDoesntClickLaunchesIntent() {
String restriction = "RESTRICTION";
mTile.getState().disabledByPolicy = true;
EnforcedAdmin admin = EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(restriction);
mTile.setEnforcedAdmin(admin);
mTile.click(null);
mTestableLooper.processAllMessages();
assertFalse(mTile.mClicked);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mActivityStarter).postStartActivityDismissingKeyguard(captor.capture(), anyInt());
assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, captor.getValue().getAction());
}
@Test
public void testIsListening() {
Object o = new Object();
mTile.setListening(o, true);
mTestableLooper.processAllMessages();
assertTrue(mTile.isListening());
mTile.setListening(o, false);
mTestableLooper.processAllMessages();
assertFalse(mTile.isListening());
mTile.setListening(o, true);
mTile.destroy();
mTestableLooper.processAllMessages();
assertFalse(mTile.isListening());
}
@Test
public void testStaleTriggeredWhileListening() throws Exception {
Object o = new Object();
mTile.clearRefreshes();
mTile.setListening(o, true); // +1 refresh
mTestableLooper.processAllMessages();
mTestableLooper.runWithLooper(() -> mTile.handleStale()); // +1 refresh
mTestableLooper.processAllMessages();
mTile.setListening(o, false);
mTestableLooper.processAllMessages();
assertFalse(mTile.isListening());
assertThat(mTile.mRefreshes).isEqualTo(2);
}
@Test
public void testStaleTriggeredWhileNotListening() throws Exception {
mTile.clearRefreshes();
mTestableLooper.runWithLooper(() -> mTile.handleStale()); // +1 refresh
mTestableLooper.processAllMessages();
assertFalse(mTile.isListening());
assertThat(mTile.mRefreshes).isEqualTo(1);
}
private void assertEvent(UiEventLogger.UiEventEnum eventType,
UiEventLoggerFake.FakeUiEvent fakeEvent) {
assertEquals(eventType.getId(), fakeEvent.eventId);
assertEquals(SPEC, fakeEvent.packageName);
assertEquals(mInstanceId, fakeEvent.instanceId);
}
private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
private final int mCategory;
public String mInvalid;
public TileLogMatcher(int category) {
mCategory = category;
}
@Override
public boolean matches(LogMaker arg) {
if (arg.getCategory() != mCategory) {
mInvalid = "Expected category " + mCategory + " but was " + arg.getCategory();
return false;
}
if (arg.getType() != TYPE_ACTION) {
mInvalid = "Expected type " + TYPE_ACTION + " but was " + arg.getType();
return false;
}
if (arg.getSubtype() != mTile.getMetricsCategory()) {
mInvalid = "Expected subtype " + mTile.getMetricsCategory() + " but was "
+ arg.getSubtype();
return false;
}
return true;
}
@Override
public String toString() {
return mInvalid;
}
}
private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
boolean mClicked;
int mRefreshes = 0;
protected TileImpl(
QSHost host,
Looper backgroundLooper,
Handler mainHandler,
FalsingManager falsingManager,
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
getState().state = Tile.STATE_ACTIVE;
}
public void setEnforcedAdmin(EnforcedAdmin admin) {
mEnforcedAdmin = admin;
}
@Override
public BooleanState newTileState() {
return new BooleanState();
}
@Override
protected void handleClick(@Nullable View view) {
mClicked = true;
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
mRefreshes++;
}
void clearRefreshes() {
mRefreshes = 0;
}
@Override
public int getMetricsCategory() {
return 42;
}
@Override
public Intent getLongClickIntent() {
return null;
}
@Override
public CharSequence getTileLabel() {
return null;
}
}
}