1/* 2 * Copyright (C) 2015 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 */ 16package androidx.leanback.app; 17 18import static org.junit.Assert.assertEquals; 19import static org.junit.Assert.assertNotNull; 20import static org.junit.Assert.assertNull; 21import static org.junit.Assert.assertTrue; 22import static org.mockito.Matchers.any; 23import static org.mockito.Mockito.timeout; 24import static org.mockito.Mockito.verify; 25 26import android.app.Instrumentation; 27import android.content.Intent; 28import android.os.Build; 29import android.os.Bundle; 30import android.os.SystemClock; 31import android.support.test.InstrumentationRegistry; 32import android.support.test.filters.LargeTest; 33import android.support.test.rule.ActivityTestRule; 34import android.support.test.runner.AndroidJUnit4; 35import android.view.KeyEvent; 36import android.view.LayoutInflater; 37import android.view.MotionEvent; 38import android.view.View; 39import android.view.ViewGroup; 40import android.widget.FrameLayout; 41 42import androidx.annotation.NonNull; 43import androidx.annotation.Nullable; 44import androidx.fragment.app.Fragment; 45import androidx.leanback.testutils.PollingCheck; 46import androidx.leanback.widget.ArrayObjectAdapter; 47import androidx.leanback.widget.ItemBridgeAdapter; 48import androidx.leanback.widget.ListRowPresenter; 49import androidx.leanback.widget.Presenter; 50import androidx.leanback.widget.Row; 51import androidx.leanback.widget.RowPresenter; 52import androidx.recyclerview.widget.RecyclerView; 53 54import org.junit.After; 55import org.junit.Rule; 56import org.junit.Test; 57import org.junit.runner.RunWith; 58import org.mockito.Mockito; 59 60@LargeTest 61@RunWith(AndroidJUnit4.class) 62public class BrowseSupportFragmentTest { 63 64 static final String TAG = "BrowseSupportFragmentTest"; 65 static final long WAIT_TRANSIITON_TIMEOUT = 10000; 66 67 @Rule 68 public ActivityTestRule<BrowseSupportFragmentTestActivity> activityTestRule = 69 new ActivityTestRule<>(BrowseSupportFragmentTestActivity.class, false, false); 70 private BrowseSupportFragmentTestActivity mActivity; 71 72 @After 73 public void afterTest() throws Throwable { 74 activityTestRule.runOnUiThread(new Runnable() { 75 @Override 76 public void run() { 77 if (mActivity != null) { 78 mActivity.finish(); 79 mActivity = null; 80 } 81 } 82 }); 83 } 84 85 void waitForEntranceTransitionFinished() { 86 PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.PollingCheckCondition() { 87 @Override 88 public boolean canProceed() { 89 if (Build.VERSION.SDK_INT >= 21) { 90 return mActivity.getBrowseTestSupportFragment() != null 91 && mActivity.getBrowseTestSupportFragment().mEntranceTransitionEnded; 92 } else { 93 // when entrance transition not supported, wait main fragment loaded. 94 return mActivity.getBrowseTestSupportFragment() != null 95 && mActivity.getBrowseTestSupportFragment().getMainFragment() != null; 96 } 97 } 98 }); 99 } 100 101 void waitForHeaderTransitionFinished() { 102 View row = mActivity.getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder( 103 mActivity.getBrowseTestSupportFragment().getSelectedPosition()).view; 104 PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.ViewStableOnScreen(row)); 105 PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.PollingCheckCondition() { 106 public boolean canProceed() { 107 return !mActivity.getBrowseTestSupportFragment().isInHeadersTransition(); 108 } 109 }); 110 } 111 112 @Test 113 public void testTouchMode() throws Throwable { 114 Intent intent = new Intent(); 115 intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true); 116 intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY , 0L); 117 mActivity = activityTestRule.launchActivity(intent); 118 119 waitForEntranceTransitionFinished(); 120 121 ListRowPresenter.ViewHolder rowVh = (ListRowPresenter.ViewHolder) mActivity 122 .getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(0); 123 View card = rowVh.getGridView().getChildAt(0); 124 tapView(card); 125 waitForHeaderTransitionFinished(); 126 assertTrue(card.hasFocus()); 127 assertTrue(card.isInTouchMode()); 128 sendKeys(KeyEvent.KEYCODE_BACK); 129 waitForHeaderTransitionFinished(); 130 assertTrue((mActivity.getBrowseTestSupportFragment().getHeadersSupportFragment() 131 .getVerticalGridView().getChildAt(0)).hasFocus()); 132 } 133 134 @Test 135 public void testTwoBackKeysWithBackStack() throws Throwable { 136 final long dataLoadingDelay = 0L; 137 Intent intent = new Intent(); 138 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay); 139 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true); 140 mActivity = activityTestRule.launchActivity(intent); 141 142 waitForEntranceTransitionFinished(); 143 144 assertNotNull(mActivity.getBrowseTestSupportFragment().getMainFragment()); 145 sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT); 146 waitForHeaderTransitionFinished(); 147 sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK); 148 } 149 150 @Test 151 public void testTwoBackKeysWithoutBackStack() throws Throwable { 152 final long dataLoadingDelay = 0L; 153 Intent intent = new Intent(); 154 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay); 155 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false); 156 mActivity = activityTestRule.launchActivity(intent); 157 158 waitForEntranceTransitionFinished(); 159 160 assertNotNull(mActivity.getBrowseTestSupportFragment().getMainFragment()); 161 sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT); 162 waitForHeaderTransitionFinished(); 163 sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK); 164 } 165 166 @Test 167 public void testPressRightBeforeMainFragmentCreated() throws Throwable { 168 final long dataLoadingDelay = 1000; 169 Intent intent = new Intent(); 170 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay); 171 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false); 172 mActivity = activityTestRule.launchActivity(intent); 173 174 assertNull(mActivity.getBrowseTestSupportFragment().getMainFragment()); 175 sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT); 176 } 177 178 public static class MyRow extends Row { 179 } 180 181 public static class MyFragment extends Fragment implements 182 BrowseSupportFragment.MainFragmentAdapterProvider { 183 BrowseSupportFragment.MainFragmentAdapter mMainFragmentAdapter = new BrowseSupportFragment 184 .MainFragmentAdapter(this); 185 186 @Nullable 187 @Override 188 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 189 @Nullable Bundle savedInstanceState) { 190 return new FrameLayout(container.getContext()); 191 } 192 193 @Override 194 public BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter() { 195 return mMainFragmentAdapter; 196 } 197 } 198 199 public static class MyFragmentFactory extends 200 BrowseSupportFragment.FragmentFactory<MyFragment> { 201 public MyFragment createFragment(Object row) { 202 return new MyFragment(); 203 } 204 } 205 206 @Test 207 public void testPressCenterBeforeMainFragmentCreated() throws Throwable { 208 Intent intent = new Intent(); 209 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, 0L); 210 mActivity = activityTestRule.launchActivity(intent); 211 212 final BrowseSupportFragment fragment = mActivity.getBrowseTestSupportFragment(); 213 fragment.getMainFragmentRegistry().registerFragment(MyRow.class, new MyFragmentFactory()); 214 215 final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new RowPresenter() { 216 protected ViewHolder createRowViewHolder(ViewGroup parent) { 217 View view = new FrameLayout(parent.getContext()); 218 return new RowPresenter.ViewHolder(view); 219 } 220 }); 221 adapter.add(new MyRow()); 222 activityTestRule.runOnUiThread(new Runnable() { 223 @Override 224 public void run() { 225 fragment.setAdapter(adapter); 226 } 227 }); 228 activityTestRule.runOnUiThread(new Runnable() { 229 @Override 230 public void run() { 231 KeyEvent kv; 232 kv = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 233 fragment.getView().dispatchKeyEvent(kv); 234 kv = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 235 fragment.getView().dispatchKeyEvent(kv); 236 } 237 }); 238 } 239 240 @Test 241 public void testSelectCardOnARow() throws Throwable { 242 final int selectRow = 10; 243 final int selectItem = 20; 244 Intent intent = new Intent(); 245 final long dataLoadingDelay = 0L; 246 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay); 247 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true); 248 mActivity = activityTestRule.launchActivity(intent); 249 250 waitForEntranceTransitionFinished(); 251 252 Presenter.ViewHolderTask itemTask = Mockito.spy( 253 new ItemSelectionTask(mActivity, selectRow)); 254 255 final ListRowPresenter.SelectItemViewHolderTask task = 256 new ListRowPresenter.SelectItemViewHolderTask(selectItem); 257 task.setItemTask(itemTask); 258 259 mActivity.runOnUiThread(new Runnable() { 260 @Override 261 public void run() { 262 mActivity.getBrowseTestSupportFragment().setSelectedPosition(selectRow, true, task); 263 } 264 }); 265 266 verify(itemTask, timeout(5000).times(1)).run(any(Presenter.ViewHolder.class)); 267 268 activityTestRule.runOnUiThread(new Runnable() { 269 @Override 270 public void run() { 271 ListRowPresenter.ViewHolder row = (ListRowPresenter.ViewHolder) mActivity 272 .getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(selectRow); 273 assertNotNull(dumpRecyclerView(mActivity.getBrowseTestSupportFragment().getGridView()), row); 274 assertNotNull(row.getGridView()); 275 assertEquals(selectItem, row.getGridView().getSelectedPosition()); 276 } 277 }); 278 } 279 280 @Test 281 public void activityRecreate_notCrash() throws Throwable { 282 final long dataLoadingDelay = 0L; 283 Intent intent = new Intent(); 284 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay); 285 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false); 286 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, true); 287 mActivity = activityTestRule.launchActivity(intent); 288 289 waitForEntranceTransitionFinished(); 290 291 InstrumentationRegistry.getInstrumentation().callActivityOnRestart(mActivity); 292 activityTestRule.runOnUiThread(new Runnable() { 293 @Override 294 public void run() { 295 mActivity.recreate(); 296 } 297 }); 298 } 299 300 301 @Test 302 public void lateLoadingHeaderDisabled() throws Throwable { 303 final long dataLoadingDelay = 0L; 304 Intent intent = new Intent(); 305 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay); 306 intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_HEADERS_STATE, 307 BrowseSupportFragment.HEADERS_DISABLED); 308 mActivity = activityTestRule.launchActivity(intent); 309 waitForEntranceTransitionFinished(); 310 PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { 311 @Override 312 public boolean canProceed() { 313 return mActivity.getBrowseTestSupportFragment().getGridView() != null 314 && mActivity.getBrowseTestSupportFragment().getGridView().getChildCount() > 0; 315 } 316 }); 317 } 318 319 static void tapView(View v) { 320 Instrumentation inst = InstrumentationRegistry.getInstrumentation(); 321 int[] xy = new int[2]; 322 v.getLocationOnScreen(xy); 323 324 final int viewWidth = v.getWidth(); 325 final int viewHeight = v.getHeight(); 326 327 final float x = xy[0] + (viewWidth / 2.0f); 328 float y = xy[1] + (viewHeight / 2.0f); 329 330 long downTime = SystemClock.uptimeMillis(); 331 long eventTime = SystemClock.uptimeMillis(); 332 333 MotionEvent event = MotionEvent.obtain(downTime, eventTime, 334 MotionEvent.ACTION_DOWN, x, y, 0); 335 inst.sendPointerSync(event); 336 inst.waitForIdleSync(); 337 338 eventTime = SystemClock.uptimeMillis(); 339 event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); 340 inst.sendPointerSync(event); 341 inst.waitForIdleSync(); 342 } 343 344 private void sendKeys(int ...keys) { 345 for (int i = 0; i < keys.length; i++) { 346 InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]); 347 } 348 } 349 350 public static class ItemSelectionTask extends Presenter.ViewHolderTask { 351 352 private final BrowseSupportFragmentTestActivity activity; 353 private final int expectedRow; 354 355 public ItemSelectionTask(BrowseSupportFragmentTestActivity activity, int expectedRow) { 356 this.activity = activity; 357 this.expectedRow = expectedRow; 358 } 359 360 @Override 361 public void run(Presenter.ViewHolder holder) { 362 android.util.Log.d(TAG, dumpRecyclerView(activity.getBrowseTestSupportFragment() 363 .getGridView())); 364 android.util.Log.d(TAG, "Row " + expectedRow + " " + activity.getBrowseTestSupportFragment() 365 .getRowsSupportFragment().getRowViewHolder(expectedRow), new Exception()); 366 } 367 } 368 369 static String dumpRecyclerView(RecyclerView recyclerView) { 370 StringBuffer b = new StringBuffer(); 371 for (int i = 0; i < recyclerView.getChildCount(); i++) { 372 View child = recyclerView.getChildAt(i); 373 ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) 374 recyclerView.getChildViewHolder(child); 375 b.append("child").append(i).append(":").append(vh); 376 if (vh != null) { 377 b.append(",").append(vh.getViewHolder()); 378 } 379 b.append(";"); 380 } 381 return b.toString(); 382 } 383} 384