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