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 17 package android.widget; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.drawable.BitmapDrawable; 22 import android.graphics.drawable.Drawable; 23 import android.os.AsyncTask; 24 import android.os.Parcel; 25 import android.support.test.InstrumentationRegistry; 26 import android.support.test.filters.SmallTest; 27 import android.support.test.runner.AndroidJUnit4; 28 import android.view.View; 29 import android.view.ViewGroup; 30 31 import com.android.frameworks.coretests.R; 32 33 import org.junit.Before; 34 import org.junit.Rule; 35 import org.junit.Test; 36 import org.junit.rules.ExpectedException; 37 import org.junit.runner.RunWith; 38 39 import static org.junit.Assert.assertEquals; 40 import static org.junit.Assert.assertSame; 41 import static org.junit.Assert.assertTrue; 42 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.concurrent.CountDownLatch; 46 47 /** 48 * Tests for RemoteViews. 49 */ 50 @RunWith(AndroidJUnit4.class) 51 @SmallTest 52 public 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 setup()65 public void setup() { 66 mContext = InstrumentationRegistry.getContext(); 67 mPackage = mContext.getPackageName(); 68 mContainer = new LinearLayout(mContext); 69 } 70 71 @Test clone_doesNotCopyBitmap()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 clone_originalCanStillBeApplied()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 clone_clones()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 clone_child_fails()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 clone_repeatedly()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 clone_chained()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 parcelSize_nestedViews()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 parcelSize_differentOrientation()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 exception.expect(Exception.class); 183 listener.waitAndGetView(); 184 } 185 186 @Test 187 public void asyncApply() throws Exception { 188 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); 189 views.setTextViewText(R.id.text, "Dummy"); 190 191 View syncView = views.apply(mContext, mContainer); 192 193 ViewAppliedListener listener = new ViewAppliedListener(); 194 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener); 195 View asyncView = listener.waitAndGetView(); 196 197 verifyViewTree(syncView, asyncView, "Dummy"); 198 } 199 200 @Test 201 public void asyncApply_viewStub() throws Exception { 202 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_viewstub); 203 views.setInt(R.id.viewStub, "setLayoutResource", R.layout.remote_views_text); 204 // This will cause the view to be inflated 205 views.setViewVisibility(R.id.viewStub, View.INVISIBLE); 206 views.setTextViewText(R.id.stub_inflated, "Dummy"); 207 208 View syncView = views.apply(mContext, mContainer); 209 210 ViewAppliedListener listener = new ViewAppliedListener(); 211 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener); 212 View asyncView = listener.waitAndGetView(); 213 214 verifyViewTree(syncView, asyncView, "Dummy"); 215 } 216 217 @Test 218 public void asyncApply_nestedViews() throws Exception { 219 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_view_host); 220 views.removeAllViews(R.id.container); 221 views.addView(R.id.container, createViewChained(1, "row1-c1", "row1-c2", "row1-c3")); 222 views.addView(R.id.container, createViewChained(5, "row2-c1", "row2-c2")); 223 views.addView(R.id.container, createViewChained(2, "row3-c1", "row3-c2")); 224 225 View syncView = views.apply(mContext, mContainer); 226 227 ViewAppliedListener listener = new ViewAppliedListener(); 228 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener); 229 View asyncView = listener.waitAndGetView(); 230 231 verifyViewTree(syncView, asyncView, 232 "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2", "row3-c1", "row3-c2"); 233 } 234 235 @Test 236 public void asyncApply_viewstub_nestedViews() throws Exception { 237 RemoteViews viewstub = new RemoteViews(mPackage, R.layout.remote_views_viewstub); 238 viewstub.setInt(R.id.viewStub, "setLayoutResource", R.layout.remote_view_host); 239 // This will cause the view to be inflated 240 viewstub.setViewVisibility(R.id.viewStub, View.INVISIBLE); 241 viewstub.addView(R.id.stub_inflated, createViewChained(1, "row1-c1", "row1-c2", "row1-c3")); 242 243 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_view_host); 244 views.removeAllViews(R.id.container); 245 views.addView(R.id.container, viewstub); 246 views.addView(R.id.container, createViewChained(5, "row2-c1", "row2-c2")); 247 248 View syncView = views.apply(mContext, mContainer); 249 250 ViewAppliedListener listener = new ViewAppliedListener(); 251 views.applyAsync(mContext, mContainer, AsyncTask.THREAD_POOL_EXECUTOR, listener); 252 View asyncView = listener.waitAndGetView(); 253 254 verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2"); 255 } 256 257 private RemoteViews createViewChained(int depth, String... texts) { 258 RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host); 259 260 // Create depth 261 RemoteViews parent = result; 262 while(depth > 0) { 263 depth--; 264 RemoteViews child = new RemoteViews(mPackage, R.layout.remote_view_host); 265 parent.addView(R.id.container, child); 266 parent = child; 267 } 268 269 // Add texts 270 for (String text : texts) { 271 RemoteViews child = new RemoteViews(mPackage, R.layout.remote_views_text); 272 child.setTextViewText(R.id.text, text); 273 parent.addView(R.id.container, child); 274 } 275 return result; 276 } 277 verifyViewTree(View v1, View v2, String... texts)278 private void verifyViewTree(View v1, View v2, String... texts) { 279 ArrayList<String> expectedTexts = new ArrayList<>(Arrays.asList(texts)); 280 verifyViewTreeRecur(v1, v2, expectedTexts); 281 // Verify that all expected texts were found 282 assertEquals(0, expectedTexts.size()); 283 } 284 verifyViewTreeRecur(View v1, View v2, ArrayList<String> expectedTexts)285 private void verifyViewTreeRecur(View v1, View v2, ArrayList<String> expectedTexts) { 286 assertEquals(v1.getClass(), v2.getClass()); 287 288 if (v1 instanceof TextView) { 289 String text = ((TextView) v1).getText().toString(); 290 assertEquals(text, ((TextView) v2).getText().toString()); 291 // Verify that the text was one of the expected texts and remove it from the list 292 assertTrue(expectedTexts.remove(text)); 293 } else if (v1 instanceof ViewGroup) { 294 ViewGroup vg1 = (ViewGroup) v1; 295 ViewGroup vg2 = (ViewGroup) v2; 296 assertEquals(vg1.getChildCount(), vg2.getChildCount()); 297 for (int i = vg1.getChildCount() - 1; i >= 0; i--) { 298 verifyViewTreeRecur(vg1.getChildAt(i), vg2.getChildAt(i), expectedTexts); 299 } 300 } 301 } 302 303 private class ViewAppliedListener implements RemoteViews.OnViewAppliedListener { 304 305 private final CountDownLatch mLatch = new CountDownLatch(1); 306 private View mView; 307 private Exception mError; 308 309 @Override onViewApplied(View v)310 public void onViewApplied(View v) { 311 mView = v; 312 mLatch.countDown(); 313 } 314 315 @Override onError(Exception e)316 public void onError(Exception e) { 317 mError = e; 318 mLatch.countDown(); 319 } 320 waitAndGetView()321 public View waitAndGetView() throws Exception { 322 mLatch.await(); 323 324 if (mError != null) { 325 throw new Exception(mError); 326 } 327 return mView; 328 } 329 } 330 331 @Test nestedAddViews()332 public void nestedAddViews() { 333 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); 334 for (int i = 0; i < 10; i++) { 335 RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_views_test); 336 parent.addView(R.id.layout, views); 337 views = parent; 338 } 339 views.clone(); 340 341 views = new RemoteViews(mPackage, R.layout.remote_views_test); 342 for (int i = 0; i < 11; i++) { 343 RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_views_test); 344 parent.addView(R.id.layout, views); 345 views = parent; 346 } 347 exception.expect(IllegalArgumentException.class); 348 views.clone(); 349 } 350 351 @Test nestedLandscapeViews()352 public void nestedLandscapeViews() { 353 RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); 354 for (int i = 0; i < 10; i++) { 355 views = new RemoteViews(views, 356 new RemoteViews(mPackage, R.layout.remote_views_test)); 357 } 358 views.clone(); 359 360 views = new RemoteViews(mPackage, R.layout.remote_views_test); 361 for (int i = 0; i < 11; i++) { 362 RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_views_test); 363 parent.addView(R.id.layout, views); 364 views = parent; 365 } 366 exception.expect(IllegalArgumentException.class); 367 views.clone(); 368 } 369 } 370