blob: 70cf097f42a346d7078d22e32ade6392bd1e9e99 [file] [log] [blame]
Adrian Roos7da889d2016-03-16 18:38:58 -07001/*
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
Kirill Grouchnikov8376f9e2016-07-08 11:27:03 -040017package android.widget;
Adrian Roos7da889d2016-03-16 18:38:58 -070018
Sunny Goyal56333a82017-08-29 13:46:29 -070019import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertSame;
21import static org.junit.Assert.assertTrue;
22
Sunny Goyal5b7689f2017-09-21 11:08:34 -070023import android.app.PendingIntent;
Adrian Roos7da889d2016-03-16 18:38:58 -070024import android.content.Context;
Sunny Goyal5b7689f2017-09-21 11:08:34 -070025import android.content.Intent;
Adrian Roos7da889d2016-03-16 18:38:58 -070026import android.graphics.Bitmap;
27import android.graphics.drawable.BitmapDrawable;
28import android.graphics.drawable.Drawable;
Sunny Goyal7b0e2c72016-11-03 14:48:05 -070029import android.os.AsyncTask;
Adrian Roosfb921842017-10-26 14:49:56 +020030import android.os.Binder;
Sunny Goyal5d8bcdf2016-10-27 16:11:29 -070031import android.os.Parcel;
Adrian Roos7da889d2016-03-16 18:38:58 -070032import android.support.test.InstrumentationRegistry;
33import android.support.test.filters.SmallTest;
34import android.support.test.runner.AndroidJUnit4;
Kirill Grouchnikov8376f9e2016-07-08 11:27:03 -040035import android.view.View;
Sunny Goyal7b0e2c72016-11-03 14:48:05 -070036import android.view.ViewGroup;
Adrian Roos7da889d2016-03-16 18:38:58 -070037
38import com.android.frameworks.coretests.R;
39
Kirill Grouchnikov8376f9e2016-07-08 11:27:03 -040040import org.junit.Before;
41import org.junit.Rule;
42import org.junit.Test;
43import org.junit.rules.ExpectedException;
44import org.junit.runner.RunWith;
45
Sunny Goyal7b0e2c72016-11-03 14:48:05 -070046import java.util.ArrayList;
47import java.util.Arrays;
48import java.util.concurrent.CountDownLatch;
49
Adrian Roos7da889d2016-03-16 18:38:58 -070050/**
51 * Tests for RemoteViews.
52 */
53@RunWith(AndroidJUnit4.class)
54@SmallTest
55public class RemoteViewsTest {
56
Sunny Goyal5d8bcdf2016-10-27 16:11:29 -070057 // This can point to any other package which exists on the device.
58 private static final String OTHER_PACKAGE = "com.android.systemui";
59
Adrian Roos7da889d2016-03-16 18:38:58 -070060 @Rule
61 public final ExpectedException exception = ExpectedException.none();
62
63 private Context mContext;
64 private String mPackage;
65 private LinearLayout mContainer;
66
67 @Before
68 public void setup() {
69 mContext = InstrumentationRegistry.getContext();
Adrian Roos4d10db72016-05-23 14:32:39 -070070 mPackage = mContext.getPackageName();
Adrian Roos7da889d2016-03-16 18:38:58 -070071 mContainer = new LinearLayout(mContext);
72 }
73
74 @Test
75 public void clone_doesNotCopyBitmap() {
76 RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
77 Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
78
79 original.setImageViewBitmap(R.id.image, bitmap);
80 RemoteViews clone = original.clone();
81 View inflated = clone.apply(mContext, mContainer);
82
83 Drawable drawable = ((ImageView) inflated.findViewById(R.id.image)).getDrawable();
84 assertSame(bitmap, ((BitmapDrawable)drawable).getBitmap());
85 }
86
87 @Test
88 public void clone_originalCanStillBeApplied() {
89 RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
90
91 RemoteViews clone = original.clone();
92
93 clone.apply(mContext, mContainer);
94 }
95
96 @Test
97 public void clone_clones() {
98 RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
99
100 RemoteViews clone = original.clone();
101 original.setTextViewText(R.id.text, "test");
102 View inflated = clone.apply(mContext, mContainer);
103
104 TextView textView = (TextView) inflated.findViewById(R.id.text);
105 assertEquals("", textView.getText());
106 }
107
108 @Test
109 public void clone_child_fails() {
110 RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
111 RemoteViews child = new RemoteViews(mPackage, R.layout.remote_views_test);
112
113 original.addView(R.id.layout, child);
114
115 exception.expect(IllegalStateException.class);
116 RemoteViews clone = child.clone();
117 }
118
119 @Test
120 public void clone_repeatedly() {
121 RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
122
123 original.clone();
124 original.clone();
125
126 original.apply(mContext, mContainer);
127 }
128
129 @Test
130 public void clone_chained() {
131 RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
132
133 RemoteViews clone = original.clone().clone();
134
135 clone.apply(mContext, mContainer);
136 }
137
Sunny Goyal5d8bcdf2016-10-27 16:11:29 -0700138 @Test
139 public void parcelSize_nestedViews() {
140 RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
141 // We don't care about the actual layout id.
142 RemoteViews child = new RemoteViews(mPackage, 33);
143 int expectedSize = getParcelSize(original) + getParcelSize(child);
144 original.addView(R.id.layout, child);
145
146 // The application info will get written only once.
147 assertTrue(getParcelSize(original) < expectedSize);
148 assertEquals(getParcelSize(original), getParcelSize(original.clone()));
149
150 original = new RemoteViews(mPackage, R.layout.remote_views_test);
151 child = new RemoteViews(OTHER_PACKAGE, 33);
152 expectedSize = getParcelSize(original) + getParcelSize(child);
153 original.addView(R.id.layout, child);
154
155 // Both the views will get written completely along with an additional view operation
156 assertTrue(getParcelSize(original) > expectedSize);
157 assertEquals(getParcelSize(original), getParcelSize(original.clone()));
158 }
159
160 @Test
161 public void parcelSize_differentOrientation() {
162 RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test);
163 RemoteViews portrait = new RemoteViews(mPackage, 33);
164
165 // The application info will get written only once.
166 RemoteViews views = new RemoteViews(landscape, portrait);
167 assertTrue(getParcelSize(views) < (getParcelSize(landscape) + getParcelSize(portrait)));
168 assertEquals(getParcelSize(views), getParcelSize(views.clone()));
169 }
170
171 private int getParcelSize(RemoteViews view) {
172 Parcel parcel = Parcel.obtain();
173 view.writeToParcel(parcel, 0);
174 int size = parcel.dataSize();
175 parcel.recycle();
176 return size;
177 }
Sunny Goyal7b0e2c72016-11-03 14:48:05 -0700178
179 @Test
180 public void asyncApply_fail() throws Exception {
181 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_view_test_bad_1);
182 ViewAppliedListener listener = new ViewAppliedListener();
183 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
184
Sunny Goyal692f8c92016-11-11 09:19:14 -0800185 exception.expect(Exception.class);
186 listener.waitAndGetView();
Sunny Goyal7b0e2c72016-11-03 14:48:05 -0700187 }
188
189 @Test
190 public void asyncApply() throws Exception {
191 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
192 views.setTextViewText(R.id.text, "Dummy");
193
194 View syncView = views.apply(mContext, mContainer);
195
196 ViewAppliedListener listener = new ViewAppliedListener();
197 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
198 View asyncView = listener.waitAndGetView();
199
200 verifyViewTree(syncView, asyncView, "Dummy");
201 }
202
203 @Test
204 public void asyncApply_viewStub() throws Exception {
205 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_viewstub);
206 views.setInt(R.id.viewStub, "setLayoutResource", R.layout.remote_views_text);
207 // This will cause the view to be inflated
208 views.setViewVisibility(R.id.viewStub, View.INVISIBLE);
209 views.setTextViewText(R.id.stub_inflated, "Dummy");
210
211 View syncView = views.apply(mContext, mContainer);
212
213 ViewAppliedListener listener = new ViewAppliedListener();
214 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
215 View asyncView = listener.waitAndGetView();
216
217 verifyViewTree(syncView, asyncView, "Dummy");
218 }
219
220 @Test
221 public void asyncApply_nestedViews() throws Exception {
222 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_view_host);
223 views.removeAllViews(R.id.container);
224 views.addView(R.id.container, createViewChained(1, "row1-c1", "row1-c2", "row1-c3"));
225 views.addView(R.id.container, createViewChained(5, "row2-c1", "row2-c2"));
226 views.addView(R.id.container, createViewChained(2, "row3-c1", "row3-c2"));
227
228 View syncView = views.apply(mContext, mContainer);
229
230 ViewAppliedListener listener = new ViewAppliedListener();
231 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
232 View asyncView = listener.waitAndGetView();
233
234 verifyViewTree(syncView, asyncView,
235 "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2", "row3-c1", "row3-c2");
236 }
237
238 @Test
239 public void asyncApply_viewstub_nestedViews() throws Exception {
240 RemoteViews viewstub = new RemoteViews(mPackage, R.layout.remote_views_viewstub);
241 viewstub.setInt(R.id.viewStub, "setLayoutResource", R.layout.remote_view_host);
242 // This will cause the view to be inflated
243 viewstub.setViewVisibility(R.id.viewStub, View.INVISIBLE);
244 viewstub.addView(R.id.stub_inflated, createViewChained(1, "row1-c1", "row1-c2", "row1-c3"));
245
246 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_view_host);
247 views.removeAllViews(R.id.container);
248 views.addView(R.id.container, viewstub);
249 views.addView(R.id.container, createViewChained(5, "row2-c1", "row2-c2"));
250
251 View syncView = views.apply(mContext, mContainer);
252
253 ViewAppliedListener listener = new ViewAppliedListener();
254 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener);
255 View asyncView = listener.waitAndGetView();
256
257 verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2");
258 }
259
260 private RemoteViews createViewChained(int depth, String... texts) {
261 RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
262
263 // Create depth
264 RemoteViews parent = result;
265 while(depth > 0) {
266 depth--;
267 RemoteViews child = new RemoteViews(mPackage, R.layout.remote_view_host);
268 parent.addView(R.id.container, child);
269 parent = child;
270 }
271
272 // Add texts
273 for (String text : texts) {
274 RemoteViews child = new RemoteViews(mPackage, R.layout.remote_views_text);
275 child.setTextViewText(R.id.text, text);
276 parent.addView(R.id.container, child);
277 }
278 return result;
279 }
280
281 private void verifyViewTree(View v1, View v2, String... texts) {
282 ArrayList<String> expectedTexts = new ArrayList<>(Arrays.asList(texts));
283 verifyViewTreeRecur(v1, v2, expectedTexts);
284 // Verify that all expected texts were found
285 assertEquals(0, expectedTexts.size());
286 }
287
288 private void verifyViewTreeRecur(View v1, View v2, ArrayList<String> expectedTexts) {
289 assertEquals(v1.getClass(), v2.getClass());
290
291 if (v1 instanceof TextView) {
292 String text = ((TextView) v1).getText().toString();
293 assertEquals(text, ((TextView) v2).getText().toString());
294 // Verify that the text was one of the expected texts and remove it from the list
295 assertTrue(expectedTexts.remove(text));
296 } else if (v1 instanceof ViewGroup) {
297 ViewGroup vg1 = (ViewGroup) v1;
298 ViewGroup vg2 = (ViewGroup) v2;
299 assertEquals(vg1.getChildCount(), vg2.getChildCount());
300 for (int i = vg1.getChildCount() - 1; i >= 0; i--) {
301 verifyViewTreeRecur(vg1.getChildAt(i), vg2.getChildAt(i), expectedTexts);
302 }
303 }
304 }
305
306 private class ViewAppliedListener implements RemoteViews.OnViewAppliedListener {
307
308 private final CountDownLatch mLatch = new CountDownLatch(1);
309 private View mView;
310 private Exception mError;
311
312 @Override
313 public void onViewApplied(View v) {
314 mView = v;
315 mLatch.countDown();
316 }
317
318 @Override
319 public void onError(Exception e) {
320 mError = e;
321 mLatch.countDown();
322 }
323
324 public View waitAndGetView() throws Exception {
325 mLatch.await();
326
327 if (mError != null) {
328 throw new Exception(mError);
329 }
330 return mView;
331 }
332 }
Sunny Goyal692f8c92016-11-11 09:19:14 -0800333
334 @Test
335 public void nestedAddViews() {
336 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
337 for (int i = 0; i < 10; i++) {
338 RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_views_test);
339 parent.addView(R.id.layout, views);
340 views = parent;
341 }
Sunny Goyal56333a82017-08-29 13:46:29 -0700342 // Both clone and parcel/unparcel work,
Sunny Goyal692f8c92016-11-11 09:19:14 -0800343 views.clone();
Sunny Goyal56333a82017-08-29 13:46:29 -0700344 parcelAndRecreate(views);
Sunny Goyal692f8c92016-11-11 09:19:14 -0800345
346 views = new RemoteViews(mPackage, R.layout.remote_views_test);
347 for (int i = 0; i < 11; i++) {
348 RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_views_test);
349 parent.addView(R.id.layout, views);
350 views = parent;
351 }
Sunny Goyal56333a82017-08-29 13:46:29 -0700352 // Clone works but parcel/unparcel fails
Sunny Goyal692f8c92016-11-11 09:19:14 -0800353 views.clone();
Sunny Goyal56333a82017-08-29 13:46:29 -0700354 exception.expect(IllegalArgumentException.class);
355 parcelAndRecreate(views);
Sunny Goyal692f8c92016-11-11 09:19:14 -0800356 }
357
358 @Test
359 public void nestedLandscapeViews() {
360 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
361 for (int i = 0; i < 10; i++) {
362 views = new RemoteViews(views,
363 new RemoteViews(mPackage, R.layout.remote_views_test));
364 }
Sunny Goyal56333a82017-08-29 13:46:29 -0700365 // Both clone and parcel/unparcel work,
Sunny Goyal692f8c92016-11-11 09:19:14 -0800366 views.clone();
Sunny Goyal56333a82017-08-29 13:46:29 -0700367 parcelAndRecreate(views);
Sunny Goyal692f8c92016-11-11 09:19:14 -0800368
369 views = new RemoteViews(mPackage, R.layout.remote_views_test);
370 for (int i = 0; i < 11; i++) {
Sunny Goyal56333a82017-08-29 13:46:29 -0700371 views = new RemoteViews(views,
372 new RemoteViews(mPackage, R.layout.remote_views_test));
Sunny Goyal692f8c92016-11-11 09:19:14 -0800373 }
Sunny Goyal56333a82017-08-29 13:46:29 -0700374 // Clone works but parcel/unparcel fails
Sunny Goyal692f8c92016-11-11 09:19:14 -0800375 views.clone();
Sunny Goyal56333a82017-08-29 13:46:29 -0700376 exception.expect(IllegalArgumentException.class);
377 parcelAndRecreate(views);
378 }
379
Adrian Roosfb921842017-10-26 14:49:56 +0200380 private RemoteViews parcelAndRecreate(RemoteViews views) {
381 return parcelAndRecreateWithPendingIntentCookie(views, null);
382 }
Sunny Goyal56333a82017-08-29 13:46:29 -0700383
Adrian Roosfb921842017-10-26 14:49:56 +0200384 private RemoteViews parcelAndRecreateWithPendingIntentCookie(RemoteViews views, Object cookie) {
385 Parcel p = Parcel.obtain();
386 try {
387 views.writeToParcel(p, 0);
388 p.setDataPosition(0);
389
390 if (cookie != null) {
391 p.setClassCookie(PendingIntent.class, cookie);
392 }
393
394 return RemoteViews.CREATOR.createFromParcel(p);
395 } finally {
396 p.recycle();
397 }
Sunny Goyal692f8c92016-11-11 09:19:14 -0800398 }
Sunny Goyal5b7689f2017-09-21 11:08:34 -0700399
400 @Test
401 public void copyWithBinders() throws Exception {
402 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
403 for (int i = 1; i < 10; i++) {
404 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
405 new Intent("android.widget.RemoteViewsTest_" + i), PendingIntent.FLAG_ONE_SHOT);
406 views.setOnClickPendingIntent(i, pi);
407 }
408 try {
409 new RemoteViews(views);
410 } catch (Throwable t) {
411 throw new Exception(t);
412 }
413 }
Adrian Roosfb921842017-10-26 14:49:56 +0200414
415 @Test
416 public void copy_keepsPendingIntentWhitelistToken() throws Exception {
417 Binder whitelistToken = new Binder();
418
419 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
420 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
421 new Intent("test"), PendingIntent.FLAG_ONE_SHOT);
422 views.setOnClickPendingIntent(1, pi);
423 RemoteViews withCookie = parcelAndRecreateWithPendingIntentCookie(views, whitelistToken);
424
425 RemoteViews cloned = new RemoteViews(withCookie);
426
427 PendingIntent found = extractAnyPendingIntent(cloned);
428 assertEquals(whitelistToken, found.getWhitelistToken());
429 }
430
431 private PendingIntent extractAnyPendingIntent(RemoteViews cloned) {
432 PendingIntent[] found = new PendingIntent[1];
433 Parcel p = Parcel.obtain();
434 try {
435 PendingIntent.setOnMarshaledListener((intent, parcel, flags) -> {
436 if (parcel == p) {
437 found[0] = intent;
438 }
439 });
440 cloned.writeToParcel(p, 0);
441 } finally {
442 p.recycle();
443 PendingIntent.setOnMarshaledListener(null);
444 }
445 return found[0];
446 }
Adrian Roos7da889d2016-03-16 18:38:58 -0700447}