1 /* 2 * Copyright (C) 2011 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 com.android.contacts.test; 18 19 import android.app.Activity; 20 import android.app.Instrumentation; 21 import android.content.Context; 22 import android.os.PowerManager; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.widget.TextView; 26 27 import com.google.common.base.Preconditions; 28 29 import junit.framework.Assert; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.concurrent.Callable; 34 import java.util.concurrent.ExecutionException; 35 import java.util.concurrent.FutureTask; 36 37 import javax.annotation.concurrent.GuardedBy; 38 import javax.annotation.concurrent.ThreadSafe; 39 40 /** Some utility methods for making integration testing smoother. */ 41 @ThreadSafe 42 public class IntegrationTestUtils { 43 private static final String TAG = "IntegrationTestUtils"; 44 45 private final Instrumentation mInstrumentation; 46 private final Object mLock = new Object(); 47 @GuardedBy("mLock") private PowerManager.WakeLock mWakeLock; 48 49 public IntegrationTestUtils(Instrumentation instrumentation) { 50 mInstrumentation = instrumentation; 51 } 52 53 /** 54 * Find a view by a given resource id, from the given activity, and click it, iff it is 55 * enabled according to {@link View#isEnabled()}. 56 */ 57 public void clickButton(final Activity activity, final int buttonResourceId) throws Throwable { 58 runOnUiThreadAndGetTheResult(new Callable<Void>() { 59 @Override 60 public Void call() throws Exception { 61 View view = activity.findViewById(buttonResourceId); 62 Assert.assertNotNull(view); 63 if (view.isEnabled()) { 64 view.performClick(); 65 } 66 return null; 67 } 68 }); 69 } 70 71 /** Returns the result of running {@link TextView#getText()} on the ui thread. */ 72 public CharSequence getText(final TextView view) throws Throwable { 73 return runOnUiThreadAndGetTheResult(new Callable<CharSequence>() { 74 @Override 75 public CharSequence call() { 76 return view.getText(); 77 } 78 }); 79 } 80 81 // TODO: Move this class and the appropriate documentation into a test library, having checked 82 // first to see if exactly this code already exists or not. 83 /** 84 * Execute a callable on the ui thread, returning its result synchronously. 85 * <p> 86 * Waits for an idle sync on the main thread (see {@link Instrumentation#waitForIdle(Runnable)}) 87 * before executing this callable. 88 */ 89 public <T> T runOnUiThreadAndGetTheResult(Callable<T> callable) throws Throwable { 90 FutureTask<T> future = new FutureTask<T>(callable); 91 mInstrumentation.waitForIdle(future); 92 try { 93 return future.get(); 94 } catch (ExecutionException e) { 95 // Unwrap the cause of the exception and re-throw it. 96 throw e.getCause(); 97 } 98 } 99 100 /** 101 * Wake up the screen, useful in tests that want or need the screen to be on. 102 * <p> 103 * This is usually called from setUp() for tests that require it. After calling this method, 104 * {@link #releaseScreenWakeLock()} must be called, this is usually done from tearDown(). 105 */ 106 public void acquireScreenWakeLock(Context context) { 107 synchronized (mLock) { 108 Preconditions.checkState(mWakeLock == null, "mWakeLock was already held"); 109 mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)) 110 .newWakeLock( 111 PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE | PowerManager.FULL_WAKE_LOCK, TAG); 112 mWakeLock.acquire(); 113 } 114 } 115 116 /** Release the wake lock previously acquired with {@link #acquireScreenWakeLock(Context)}. */ 117 public void releaseScreenWakeLock() { 118 synchronized (mLock) { 119 // We don't use Preconditions to force you to have acquired before release. 120 // This is because we don't want unnecessary exceptions in tearDown() since they'll 121 // typically mask the actual exception that happened during the test. 122 // The other reason is that this method is most likely to be called from tearDown(), 123 // which is invoked within a finally block, so it's not infrequently the case that 124 // the setUp() method fails before getting the lock, at which point we don't want 125 // to fail in tearDown(). 126 if (mWakeLock != null) { 127 mWakeLock.release(); 128 mWakeLock = null; 129 } 130 } 131 } 132 133 /** 134 * Gets all {@link TextView} objects whose {@link TextView#getText()} contains the given text as 135 * a substring. 136 */ 137 public List<TextView> getTextViewsWithString(final Activity activity, final String text) 138 throws Throwable { 139 return getTextViewsWithString(getRootView(activity), text); 140 } 141 142 /** 143 * Gets all {@link TextView} objects whose {@link TextView#getText()} contains the given text as 144 * a substring for the given root view. 145 */ 146 public List<TextView> getTextViewsWithString(final View rootView, final String text) 147 throws Throwable { 148 return runOnUiThreadAndGetTheResult(new Callable<List<TextView>>() { 149 @Override 150 public List<TextView> call() throws Exception { 151 List<TextView> matchingViews = new ArrayList<TextView>(); 152 for (TextView textView : getAllViews(TextView.class, rootView)) { 153 if (textView.getText().toString().contains(text)) { 154 matchingViews.add(textView); 155 } 156 } 157 return matchingViews; 158 } 159 }); 160 } 161 162 /** Find the root view for a given activity. */ 163 public static View getRootView(Activity activity) { 164 return activity.findViewById(android.R.id.content).getRootView(); 165 } 166 167 /** 168 * Gets a list of all views of a given type, rooted at the given parent. 169 * <p> 170 * This method will recurse down through all {@link ViewGroup} instances looking for 171 * {@link View} instances of the supplied class type. Specifically it will use the 172 * {@link Class#isAssignableFrom(Class)} method as the test for which views to add to the list, 173 * so if you provide {@code View.class} as your type, you will get every view. The parent itself 174 * will be included also, should it be of the right type. 175 * <p> 176 * This call manipulates the ui, and as such should only be called from the application's main 177 * thread. 178 */ 179 private static <T extends View> List<T> getAllViews(final Class<T> clazz, final View parent) { 180 List<T> results = new ArrayList<T>(); 181 if (parent.getClass().equals(clazz)) { 182 results.add(clazz.cast(parent)); 183 } 184 if (parent instanceof ViewGroup) { 185 ViewGroup viewGroup = (ViewGroup) parent; 186 for (int i = 0; i < viewGroup.getChildCount(); ++i) { 187 results.addAll(getAllViews(clazz, viewGroup.getChildAt(i))); 188 } 189 } 190 return results; 191 } 192 } 193