RemoteViewsTest.java revision 7b0e2c7659c5abd9e452cc71a6dbe0fee1d8b12f
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package android.widget;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.drawable.BitmapDrawable;
22import android.graphics.drawable.Drawable;
23import android.os.AsyncTask;
24import android.os.Parcel;
25import android.support.test.InstrumentationRegistry;
26import android.support.test.filters.SmallTest;
27import android.support.test.runner.AndroidJUnit4;
28import android.view.View;
29import android.view.ViewGroup;
30
31import com.android.frameworks.coretests.R;
32
33import org.junit.Before;
34import org.junit.Rule;
35import org.junit.Test;
36import org.junit.rules.ExpectedException;
37import org.junit.runner.RunWith;
38
39import static org.junit.Assert.assertEquals;
40import static org.junit.Assert.assertSame;
41import static org.junit.Assert.assertTrue;
42
43import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.concurrent.CountDownLatch;
46
47/**
48 * Tests for RemoteViews.
49 */
50@RunWith(AndroidJUnit4.class)
51@SmallTest
52public class RemoteViewsTest {
53
54    // This can point to any other package which exists on the device.
55    private static final String OTHER_PACKAGE = "com.android.systemui";
56
57    @Rule
58    public final ExpectedException exception = ExpectedException.none();
59
60    private Context mContext;
61    private String mPackage;
62    private LinearLayout mContainer;
63
64    @Before
65    public void setup() {
66        mContext = InstrumentationRegistry.getContext();
67        mPackage = mContext.getPackageName();
68        mContainer = new LinearLayout(mContext);
69    }
70
71    @Test
72    public void clone_doesNotCopyBitmap() {
73        RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
74        Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
75
76        original.setImageViewBitmap(R.id.image, bitmap);
77        RemoteViews clone = original.clone();
78        View inflated = clone.apply(mContext, mContainer);
79
80        Drawable drawable = ((ImageView) inflated.findViewById(R.id.image)).getDrawable();
81        assertSame(bitmap, ((BitmapDrawable)drawable).getBitmap());
82    }
83
84    @Test
85    public void clone_originalCanStillBeApplied() {
86        RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
87
88        RemoteViews clone = original.clone();
89
90        clone.apply(mContext, mContainer);
91    }
92
93    @Test
94    public void clone_clones() {
95        RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
96
97        RemoteViews clone = original.clone();
98        original.setTextViewText(R.id.text, "test");
99        View inflated = clone.apply(mContext, mContainer);
100
101        TextView textView = (TextView) inflated.findViewById(R.id.text);
102        assertEquals("", textView.getText());
103    }
104
105    @Test
106    public void clone_child_fails() {
107        RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
108        RemoteViews child = new RemoteViews(mPackage, R.layout.remote_views_test);
109
110        original.addView(R.id.layout, child);
111
112        exception.expect(IllegalStateException.class);
113        RemoteViews clone = child.clone();
114    }
115
116    @Test
117    public void clone_repeatedly() {
118        RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
119
120        original.clone();
121        original.clone();
122
123        original.apply(mContext, mContainer);
124    }
125
126    @Test
127    public void clone_chained() {
128        RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
129
130        RemoteViews clone = original.clone().clone();
131
132        clone.apply(mContext, mContainer);
133    }
134
135    @Test
136    public void parcelSize_nestedViews() {
137        RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
138        // We don't care about the actual layout id.
139        RemoteViews child = new RemoteViews(mPackage, 33);
140        int expectedSize = getParcelSize(original) + getParcelSize(child);
141        original.addView(R.id.layout, child);
142
143        // The application info will get written only once.
144        assertTrue(getParcelSize(original) < expectedSize);
145        assertEquals(getParcelSize(original), getParcelSize(original.clone()));
146
147        original = new RemoteViews(mPackage, R.layout.remote_views_test);
148        child = new RemoteViews(OTHER_PACKAGE, 33);
149        expectedSize = getParcelSize(original) + getParcelSize(child);
150        original.addView(R.id.layout, child);
151
152        // Both the views will get written completely along with an additional view operation
153        assertTrue(getParcelSize(original) > expectedSize);
154        assertEquals(getParcelSize(original), getParcelSize(original.clone()));
155    }
156
157    @Test
158    public void parcelSize_differentOrientation() {
159        RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test);
160        RemoteViews portrait = new RemoteViews(mPackage, 33);
161
162        // The application info will get written only once.
163        RemoteViews views = new RemoteViews(landscape, portrait);
164        assertTrue(getParcelSize(views) < (getParcelSize(landscape) + getParcelSize(portrait)));
165        assertEquals(getParcelSize(views), getParcelSize(views.clone()));
166    }
167
168    private int getParcelSize(RemoteViews view) {
169        Parcel parcel = Parcel.obtain();
170        view.writeToParcel(parcel, 0);
171        int size = parcel.dataSize();
172        parcel.recycle();
173        return size;
174    }
175
176    @Test
177    public void asyncApply_fail() throws Exception {
178        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_view_test_bad_1);
179        ViewAppliedListener listener = new ViewAppliedListener();
180        views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
181
182        boolean exceptionThrown = false;
183        try {
184            listener.waitAndGetView();
185        } catch (Exception e) {
186            exceptionThrown = true;
187        }
188        assertTrue(exceptionThrown);
189    }
190
191    @Test
192    public void asyncApply() throws Exception {
193        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
194        views.setTextViewText(R.id.text, "Dummy");
195
196        View syncView = views.apply(mContext, mContainer);
197
198        ViewAppliedListener listener = new ViewAppliedListener();
199        views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
200        View asyncView = listener.waitAndGetView();
201
202        verifyViewTree(syncView, asyncView, "Dummy");
203    }
204
205    @Test
206    public void asyncApply_viewStub() throws Exception {
207        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_viewstub);
208        views.setInt(R.id.viewStub, "setLayoutResource", R.layout.remote_views_text);
209        // This will cause the view to be inflated
210        views.setViewVisibility(R.id.viewStub, View.INVISIBLE);
211        views.setTextViewText(R.id.stub_inflated, "Dummy");
212
213        View syncView = views.apply(mContext, mContainer);
214
215        ViewAppliedListener listener = new ViewAppliedListener();
216        views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
217        View asyncView = listener.waitAndGetView();
218
219        verifyViewTree(syncView, asyncView, "Dummy");
220    }
221
222    @Test
223    public void asyncApply_nestedViews() throws Exception {
224        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_view_host);
225        views.removeAllViews(R.id.container);
226        views.addView(R.id.container, createViewChained(1, "row1-c1", "row1-c2", "row1-c3"));
227        views.addView(R.id.container, createViewChained(5, "row2-c1", "row2-c2"));
228        views.addView(R.id.container, createViewChained(2, "row3-c1", "row3-c2"));
229
230        View syncView = views.apply(mContext, mContainer);
231
232        ViewAppliedListener listener = new ViewAppliedListener();
233        views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
234        View asyncView = listener.waitAndGetView();
235
236        verifyViewTree(syncView, asyncView,
237                "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2", "row3-c1", "row3-c2");
238    }
239
240    @Test
241    public void asyncApply_viewstub_nestedViews() throws Exception {
242        RemoteViews viewstub = new RemoteViews(mPackage, R.layout.remote_views_viewstub);
243        viewstub.setInt(R.id.viewStub, "setLayoutResource", R.layout.remote_view_host);
244        // This will cause the view to be inflated
245        viewstub.setViewVisibility(R.id.viewStub, View.INVISIBLE);
246        viewstub.addView(R.id.stub_inflated, createViewChained(1, "row1-c1", "row1-c2", "row1-c3"));
247
248        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_view_host);
249        views.removeAllViews(R.id.container);
250        views.addView(R.id.container, viewstub);
251        views.addView(R.id.container, createViewChained(5, "row2-c1", "row2-c2"));
252
253        View syncView = views.apply(mContext, mContainer);
254
255        ViewAppliedListener listener = new ViewAppliedListener();
256        views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
257        View asyncView = listener.waitAndGetView();
258
259        verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2");
260    }
261
262    private RemoteViews createViewChained(int depth, String... texts) {
263        RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
264
265        // Create depth
266        RemoteViews parent = result;
267        while(depth > 0) {
268            depth--;
269            RemoteViews child = new RemoteViews(mPackage, R.layout.remote_view_host);
270            parent.addView(R.id.container, child);
271            parent = child;
272        }
273
274        // Add texts
275        for (String text : texts) {
276            RemoteViews child = new RemoteViews(mPackage, R.layout.remote_views_text);
277            child.setTextViewText(R.id.text, text);
278            parent.addView(R.id.container, child);
279        }
280        return result;
281    }
282
283    private void verifyViewTree(View v1, View v2, String... texts) {
284        ArrayList<String> expectedTexts = new ArrayList<>(Arrays.asList(texts));
285        verifyViewTreeRecur(v1, v2, expectedTexts);
286        // Verify that all expected texts were found
287        assertEquals(0, expectedTexts.size());
288    }
289
290    private void verifyViewTreeRecur(View v1, View v2, ArrayList<String> expectedTexts) {
291        assertEquals(v1.getClass(), v2.getClass());
292
293        if (v1 instanceof TextView) {
294            String text = ((TextView) v1).getText().toString();
295            assertEquals(text, ((TextView) v2).getText().toString());
296            // Verify that the text was one of the expected texts and remove it from the list
297            assertTrue(expectedTexts.remove(text));
298        } else if (v1 instanceof ViewGroup) {
299            ViewGroup vg1 = (ViewGroup) v1;
300            ViewGroup vg2 = (ViewGroup) v2;
301            assertEquals(vg1.getChildCount(), vg2.getChildCount());
302            for (int i = vg1.getChildCount() - 1; i >= 0; i--) {
303                verifyViewTreeRecur(vg1.getChildAt(i), vg2.getChildAt(i), expectedTexts);
304            }
305        }
306    }
307
308    private class ViewAppliedListener implements RemoteViews.OnViewAppliedListener {
309
310        private final CountDownLatch mLatch = new CountDownLatch(1);
311        private View mView;
312        private Exception mError;
313
314        @Override
315        public void onViewApplied(View v) {
316            mView = v;
317            mLatch.countDown();
318        }
319
320        @Override
321        public void onError(Exception e) {
322            mError = e;
323            mLatch.countDown();
324        }
325
326        public View waitAndGetView() throws Exception {
327            mLatch.await();
328
329            if (mError != null) {
330                throw new Exception(mError);
331            }
332            return mView;
333        }
334    }
335}
336