1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.systemui.qs.tileimpl;
16
17import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
18import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
19import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
20import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
21import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
22import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
23
24import static org.mockito.ArgumentMatchers.any;
25import static org.mockito.ArgumentMatchers.anyInt;
26import static org.mockito.ArgumentMatchers.eq;
27import static org.mockito.Matchers.argThat;
28import static org.mockito.Mockito.clearInvocations;
29import static org.mockito.Mockito.mock;
30import static org.mockito.Mockito.never;
31import static org.mockito.Mockito.spy;
32import static org.mockito.Mockito.verify;
33import static org.mockito.Mockito.when;
34
35import static java.lang.Thread.sleep;
36
37import android.content.Intent;
38import android.metrics.LogMaker;
39import android.support.test.filters.SmallTest;
40import android.support.test.InstrumentationRegistry;
41import android.testing.AndroidTestingRunner;
42import android.testing.TestableLooper;
43import android.testing.TestableLooper.RunWithLooper;
44
45import com.android.internal.logging.MetricsLogger;
46import com.android.systemui.Dependency;
47import com.android.systemui.SysuiTestCase;
48import com.android.systemui.plugins.qs.QSTile;
49import com.android.systemui.qs.QSHost;
50import com.android.systemui.qs.QSTileHost;
51
52import org.junit.Before;
53import org.junit.Ignore;
54import org.junit.Test;
55import org.junit.runner.RunWith;
56import org.mockito.ArgumentMatcher;
57
58@RunWith(AndroidTestingRunner.class)
59@RunWithLooper
60@SmallTest
61public class QSTileImplTest extends SysuiTestCase {
62
63    public static final int POSITION = 14;
64    private TestableLooper mTestableLooper;
65    private TileImpl mTile;
66    private QSTileHost mHost;
67    private MetricsLogger mMetricsLogger;
68
69    @Before
70    public void setup() throws Exception {
71        String spec = "spec";
72        mTestableLooper = TestableLooper.get(this);
73        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
74        mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
75        mHost = mock(QSTileHost.class);
76        when(mHost.indexOf(spec)).thenReturn(POSITION);
77        when(mHost.getContext()).thenReturn(mContext.getBaseContext());
78
79        mTile = spy(new TileImpl(mHost));
80        mTile.mHandler = mTile.new H(mTestableLooper.getLooper());
81        mTile.setTileSpec(spec);
82    }
83
84    @Test
85    public void testClick_Metrics() {
86        mTile.click();
87        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK)));
88    }
89
90    @Test
91    public void testSecondaryClick_Metrics() {
92        mTile.secondaryClick();
93        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
94    }
95
96    @Test
97    public void testLongClick_Metrics() {
98        mTile.longClick();
99        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
100    }
101
102    @Test
103    public void testPopulate() {
104        LogMaker maker = mock(LogMaker.class);
105        when(maker.setSubtype(anyInt())).thenReturn(maker);
106        when(maker.addTaggedData(anyInt(), any())).thenReturn(maker);
107        mTile.getState().value = true;
108        mTile.populate(maker);
109        verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
110        verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
111    }
112
113    @Test
114    @Ignore("flaky")
115    public void testStaleTimeout() throws InterruptedException {
116        when(mTile.getStaleTimeout()).thenReturn(5l);
117        clearInvocations(mTile);
118
119        mTile.handleRefreshState(null);
120        mTestableLooper.processAllMessages();
121        verify(mTile, never()).handleStale();
122
123        sleep(10);
124        mTestableLooper.processAllMessages();
125        verify(mTile).handleStale();
126    }
127
128    @Test
129    public void testStaleListening() {
130        mTile.handleStale();
131        mTestableLooper.processAllMessages();
132        verify(mTile).handleSetListening(eq(true));
133
134        mTile.handleRefreshState(null);
135        mTestableLooper.processAllMessages();
136        verify(mTile).handleSetListening(eq(false));
137    }
138
139    private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
140
141        private final int mCategory;
142        public String mInvalid;
143
144        public TileLogMatcher(int category) {
145            mCategory = category;
146        }
147
148        @Override
149        public boolean matches(LogMaker arg) {
150            if (arg.getCategory() != mCategory) {
151                mInvalid = "Expected category " + mCategory + " but was " + arg.getCategory();
152                return false;
153            }
154            if (arg.getType() != TYPE_ACTION) {
155                mInvalid = "Expected type " + TYPE_ACTION + " but was " + arg.getType();
156                return false;
157            }
158            if (arg.getSubtype() != mTile.getMetricsCategory()) {
159                mInvalid = "Expected subtype " + mTile.getMetricsCategory() + " but was "
160                        + arg.getSubtype();
161                return false;
162            }
163            return true;
164        }
165
166        @Override
167        public String toString() {
168            return mInvalid;
169        }
170    }
171
172    private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
173        protected TileImpl(QSHost host) {
174            super(host);
175        }
176
177        @Override
178        public BooleanState newTileState() {
179            return new BooleanState();
180        }
181
182        @Override
183        protected void handleClick() {
184
185        }
186
187        @Override
188        protected void handleUpdateState(BooleanState state, Object arg) {
189
190        }
191
192        @Override
193        public int getMetricsCategory() {
194            return 42;
195        }
196
197        @Override
198        public Intent getLongClickIntent() {
199            return null;
200        }
201
202        @Override
203        protected void handleSetListening(boolean listening) {
204
205        }
206
207        @Override
208        public CharSequence getTileLabel() {
209            return null;
210        }
211    }
212}
213