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.view.cts; 18 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertTrue; 22 23 import android.app.ActionBar; 24 import android.app.Activity; 25 import android.app.Instrumentation; 26 import android.content.pm.PackageManager; 27 import android.os.SystemClock; 28 import android.support.test.InstrumentationRegistry; 29 import android.support.test.filters.MediumTest; 30 import android.support.test.rule.ActivityTestRule; 31 import android.support.test.runner.AndroidJUnit4; 32 import android.view.KeyEvent; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.ViewParent; 36 37 import org.junit.Before; 38 import org.junit.Rule; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 42 @MediumTest 43 @RunWith(AndroidJUnit4.class) 44 public class ContentPaneFocusTest { 45 private Instrumentation mInstrumentation; 46 private Activity mActivity; 47 48 @Rule 49 public ActivityTestRule<ContentPaneCtsActivity> mActivityRule = 50 new ActivityTestRule<>(ContentPaneCtsActivity.class); 51 52 @Before 53 public void setup() { 54 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 55 mActivity = mActivityRule.getActivity(); 56 } 57 58 @Test 59 public void testAccessActionBar() throws Throwable { 60 final View v1 = mActivity.findViewById(R.id.view1); 61 mActivityRule.runOnUiThread(v1::requestFocus); 62 63 mInstrumentation.waitForIdleSync(); 64 sendMetaHotkey(KeyEvent.KEYCODE_TAB); 65 mInstrumentation.waitForIdleSync(); 66 67 ActionBar action = mActivity.getActionBar(); 68 if (action == null || !action.isShowing()) { 69 // No action bar, so we only needed to make sure that the shortcut didn't cause 70 // the framework to crash. 71 return; 72 } 73 74 final View actionBar = getActionBarView(); 75 // Should jump to the action bar after meta+tab 76 mActivityRule.runOnUiThread(() -> { 77 assertFalse(v1.hasFocus()); 78 assertTrue(actionBar.hasFocus()); 79 }); 80 81 boolean isTouchScreen = mActivity.getPackageManager() 82 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); 83 if (isTouchScreen) { 84 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN); 85 mInstrumentation.waitForIdleSync(); 86 87 // Shouldn't leave actionbar with normal keyboard navigation on touchscreens. 88 mActivityRule.runOnUiThread(() -> assertTrue(actionBar.hasFocus())); 89 } 90 91 sendMetaHotkey(KeyEvent.KEYCODE_TAB); 92 mInstrumentation.waitForIdleSync(); 93 94 // Should jump to the first view again. 95 mActivityRule.runOnUiThread(() -> assertTrue(v1.hasFocus())); 96 97 if (isTouchScreen) { 98 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP); 99 mInstrumentation.waitForIdleSync(); 100 // Now it shouldn't go up to action bar -- it doesn't allow taking focus once left 101 // but only for touch screens. 102 mActivityRule.runOnUiThread(() -> assertTrue(v1.hasFocus())); 103 } 104 } 105 106 @Test 107 public void testNoFocusablesInContent() throws Throwable { 108 ViewGroup top = mActivity.findViewById(R.id.linearlayout); 109 top.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 110 mActivityRule.runOnUiThread(top::clearFocus); 111 mInstrumentation.waitForIdleSync(); 112 top.clearFocus(); 113 final View content = mActivity.findViewById(android.R.id.content); 114 assertTrue(content.findFocus() == null); 115 sendMetaHotkey(KeyEvent.KEYCODE_TAB); 116 mInstrumentation.waitForIdleSync(); 117 118 ActionBar action = mActivity.getActionBar(); 119 if (action == null || !action.isShowing()) { 120 // No action bar, so we only needed to make sure that the shortcut didn't cause 121 // the framework to crash. 122 return; 123 } 124 125 assertTrue(getActionBarView().hasFocus()); 126 } 127 128 private View getActionBarView() { 129 final View content = mActivity.findViewById(android.R.id.content); 130 assertNotNull(content); 131 final ViewParent viewParent = content.getParent(); 132 assertNotNull(viewParent); 133 assertTrue(viewParent instanceof ViewGroup); 134 ViewGroup parent = (ViewGroup) viewParent; 135 View actionBarView = null; 136 for (int i = 0; i < parent.getChildCount(); i++) { 137 View child = parent.getChildAt(i); 138 if ("android:action_bar".equals(child.getTransitionName())) { 139 actionBarView = child; 140 break; 141 } 142 } 143 assertNotNull(actionBarView); 144 return actionBarView; 145 } 146 147 private void sendMetaHotkey(int keyCode) throws Throwable { 148 sendMetaKey(KeyEvent.ACTION_DOWN); 149 long time = SystemClock.uptimeMillis(); 150 KeyEvent metaHotkey = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keyCode, 151 0, KeyEvent.META_META_ON | KeyEvent.META_META_LEFT_ON); 152 mInstrumentation.sendKeySync(metaHotkey); 153 time = SystemClock.uptimeMillis(); 154 metaHotkey = new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode, 155 0, KeyEvent.META_META_ON | KeyEvent.META_META_LEFT_ON); 156 mInstrumentation.sendKeySync(metaHotkey); 157 Thread.sleep(2); 158 sendMetaKey(KeyEvent.ACTION_UP); 159 } 160 161 private void sendMetaKey(int action) throws Throwable { 162 long time = SystemClock.uptimeMillis(); 163 KeyEvent keyEvent = new KeyEvent(time, time, action, KeyEvent.KEYCODE_META_LEFT, 0, 164 KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_ON); 165 mInstrumentation.sendKeySync(keyEvent); 166 Thread.sleep(2); 167 } 168 } 169