1 /* 2 * Copyright (C) 2019 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 package com.android.launcher3.testing; 17 18 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST; 19 import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW; 20 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE; 21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 22 23 import android.annotation.TargetApi; 24 import android.app.Activity; 25 import android.content.Context; 26 import android.content.res.Resources; 27 import android.graphics.Insets; 28 import android.graphics.Point; 29 import android.graphics.Rect; 30 import android.os.Build; 31 import android.os.Bundle; 32 import android.view.WindowInsets; 33 34 import androidx.annotation.Nullable; 35 36 import com.android.launcher3.CellLayout; 37 import com.android.launcher3.DeviceProfile; 38 import com.android.launcher3.Hotseat; 39 import com.android.launcher3.InvariantDeviceProfile; 40 import com.android.launcher3.Launcher; 41 import com.android.launcher3.LauncherAppState; 42 import com.android.launcher3.LauncherState; 43 import com.android.launcher3.R; 44 import com.android.launcher3.Workspace; 45 import com.android.launcher3.dragndrop.DragLayer; 46 import com.android.launcher3.testing.shared.HotseatCellCenterRequest; 47 import com.android.launcher3.testing.shared.TestProtocol; 48 import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest; 49 import com.android.launcher3.util.ResourceBasedOverride; 50 import com.android.launcher3.widget.picker.WidgetsFullSheet; 51 52 import java.util.concurrent.Callable; 53 import java.util.concurrent.ExecutionException; 54 import java.util.concurrent.ExecutorService; 55 import java.util.function.Function; 56 import java.util.function.Supplier; 57 58 /** 59 * Class to handle requests from tests 60 */ 61 @TargetApi(Build.VERSION_CODES.Q) 62 public class TestInformationHandler implements ResourceBasedOverride { 63 newInstance(Context context)64 public static TestInformationHandler newInstance(Context context) { 65 return Overrides.getObject(TestInformationHandler.class, 66 context, R.string.test_information_handler_class); 67 } 68 69 protected Context mContext; 70 protected DeviceProfile mDeviceProfile; 71 protected LauncherAppState mLauncherAppState; 72 init(Context context)73 public void init(Context context) { 74 mContext = context; 75 mDeviceProfile = InvariantDeviceProfile.INSTANCE. 76 get(context).getDeviceProfile(context); 77 mLauncherAppState = LauncherAppState.getInstanceNoCreate(); 78 } 79 80 /** 81 * handle a request and return result Bundle. 82 * 83 * @param method request name. 84 * @param arg optional single string argument. 85 * @param extra extra request payload. 86 */ call(String method, String arg, @Nullable Bundle extra)87 public Bundle call(String method, String arg, @Nullable Bundle extra) { 88 final Bundle response = new Bundle(); 89 if (extra != null && extra.getClassLoader() == null) { 90 extra.setClassLoader(getClass().getClassLoader()); 91 } 92 switch (method) { 93 case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: { 94 return getLauncherUIProperty(Bundle::putInt, l -> { 95 final float progress = LauncherState.NORMAL.getVerticalProgress(l) 96 - LauncherState.ALL_APPS.getVerticalProgress(l); 97 final float distance = l.getAllAppsController().getShiftRange() * progress; 98 return (int) distance; 99 }); 100 } 101 102 case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: { 103 return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true); 104 } 105 106 case TestProtocol.REQUEST_FREEZE_APP_LIST: 107 return getLauncherUIProperty(Bundle::putBoolean, l -> { 108 l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST); 109 return true; 110 }); 111 case TestProtocol.REQUEST_UNFREEZE_APP_LIST: 112 return getLauncherUIProperty(Bundle::putBoolean, l -> { 113 l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST); 114 return true; 115 }); 116 117 case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: { 118 return getLauncherUIProperty(Bundle::putInt, 119 l -> l.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset()); 120 } 121 122 case TestProtocol.REQUEST_WIDGETS_SCROLL_Y: { 123 return getLauncherUIProperty(Bundle::putInt, 124 l -> WidgetsFullSheet.getWidgetsView(l).computeVerticalScrollOffset()); 125 } 126 127 case TestProtocol.REQUEST_TARGET_INSETS: { 128 return getUIProperty(Bundle::putParcelable, activity -> { 129 WindowInsets insets = activity.getWindow() 130 .getDecorView().getRootWindowInsets(); 131 return Insets.max( 132 insets.getSystemGestureInsets(), 133 insets.getSystemWindowInsets()); 134 }, this::getCurrentActivity); 135 } 136 137 case TestProtocol.REQUEST_WINDOW_INSETS: { 138 return getUIProperty(Bundle::putParcelable, activity -> { 139 WindowInsets insets = activity.getWindow() 140 .getDecorView().getRootWindowInsets(); 141 return insets.getSystemWindowInsets(); 142 }, this::getCurrentActivity); 143 } 144 145 case TestProtocol.REQUEST_ICON_HEIGHT: { 146 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, 147 mDeviceProfile.allAppsCellHeightPx); 148 return response; 149 } 150 151 case TestProtocol.REQUEST_MOCK_SENSOR_ROTATION: 152 TestProtocol.sDisableSensorRotation = true; 153 return response; 154 155 case TestProtocol.REQUEST_IS_TABLET: 156 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, mDeviceProfile.isTablet); 157 return response; 158 159 case TestProtocol.REQUEST_IS_TWO_PANELS: 160 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, 161 FOLDABLE_SINGLE_PAGE.get() ? false : mDeviceProfile.isTwoPanels); 162 return response; 163 164 case TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS: 165 response.putBoolean( 166 TestProtocol.TEST_INFO_RESPONSE_FIELD, TestLogging.sHadEventsNotFromTest); 167 return response; 168 169 case TestProtocol.REQUEST_START_DRAG_THRESHOLD: { 170 final Resources resources = mContext.getResources(); 171 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, 172 resources.getDimensionPixelSize(R.dimen.deep_shortcuts_start_drag_threshold) 173 + resources.getDimensionPixelSize(R.dimen.pre_drag_view_scale)); 174 return response; 175 } 176 177 case TestProtocol.REQUEST_ENABLE_ROTATION: 178 MAIN_EXECUTOR.submit(() -> 179 Launcher.ACTIVITY_TRACKER.getCreatedActivity().getRotationHelper() 180 .forceAllowRotationForTesting(Boolean.parseBoolean(arg))); 181 return response; 182 183 case TestProtocol.REQUEST_WORKSPACE_CELL_LAYOUT_SIZE: 184 return getLauncherUIProperty(Bundle::putIntArray, launcher -> { 185 final Workspace<?> workspace = launcher.getWorkspace(); 186 final int screenId = workspace.getScreenIdForPageIndex( 187 workspace.getCurrentPage()); 188 final CellLayout cellLayout = workspace.getScreenWithId(screenId); 189 return new int[]{cellLayout.getCountX(), cellLayout.getCountY()}; 190 }); 191 192 case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER: { 193 final WorkspaceCellCenterRequest request = extra.getParcelable( 194 TestProtocol.TEST_INFO_REQUEST_FIELD); 195 return getLauncherUIProperty(Bundle::putParcelable, launcher -> { 196 final Workspace<?> workspace = launcher.getWorkspace(); 197 // TODO(b/216387249): allow caller selecting different pages. 198 CellLayout cellLayout = (CellLayout) workspace.getPageAt( 199 workspace.getCurrentPage()); 200 final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher, 201 cellLayout, request.cellX, request.cellY, request.spanX, request.spanY); 202 return new Point(cellRect.centerX(), cellRect.centerY()); 203 }); 204 } 205 206 case TestProtocol.REQUEST_WORKSPACE_COLUMNS_ROWS: { 207 InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext); 208 return getLauncherUIProperty(Bundle::putParcelable, launcher -> new Point( 209 idp.getDeviceProfile(mContext).getPanelCount() * idp.numColumns, 210 idp.numRows 211 )); 212 } 213 214 case TestProtocol.REQUEST_WORKSPACE_CURRENT_PAGE_INDEX: { 215 return getLauncherUIProperty(Bundle::putInt, 216 launcher -> launcher.getWorkspace().getCurrentPage()); 217 } 218 219 case TestProtocol.REQUEST_HOTSEAT_CELL_CENTER: { 220 final HotseatCellCenterRequest request = extra.getParcelable( 221 TestProtocol.TEST_INFO_REQUEST_FIELD); 222 return getLauncherUIProperty(Bundle::putParcelable, launcher -> { 223 final Hotseat hotseat = launcher.getHotseat(); 224 final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher, 225 hotseat, request.cellInd, /* cellY= */ 0, 226 /* spanX= */ 1, /* spanY= */ 1); 227 // TODO(b/234322284): return the real center point. 228 return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3, 229 cellRect.top + (cellRect.bottom - cellRect.top) / 3); 230 }); 231 } 232 233 case TestProtocol.REQUEST_HAS_TIS: { 234 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false); 235 return response; 236 } 237 238 case TestProtocol.REQUEST_ALL_APPS_TOP_PADDING: { 239 return getLauncherUIProperty(Bundle::putInt, 240 l -> l.getAppsView().getActiveRecyclerView().getClipBounds().top); 241 } 242 243 case TestProtocol.REQUEST_ALL_APPS_BOTTOM_PADDING: { 244 return getLauncherUIProperty(Bundle::putInt, 245 l -> l.getAppsView().getBottom() 246 - l.getAppsView().getActiveRecyclerView().getBottom() 247 + l.getAppsView().getActiveRecyclerView().getPaddingBottom()); 248 } 249 250 case TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW: { 251 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, 252 ENABLE_GRID_ONLY_OVERVIEW.get()); 253 return response; 254 } 255 256 default: 257 return null; 258 } 259 } 260 261 private static Rect getDescendantRectRelativeToDragLayerForCell(Launcher launcher, 262 CellLayout cellLayout, int cellX, int cellY, int spanX, int spanY) { 263 final DragLayer dragLayer = launcher.getDragLayer(); 264 final Rect target = new Rect(); 265 266 cellLayout.cellToRect(cellX, cellY, spanX, spanY, target); 267 int[] leftTop = {target.left, target.top}; 268 int[] rightBottom = {target.right, target.bottom}; 269 dragLayer.getDescendantCoordRelativeToSelf(cellLayout, leftTop); 270 dragLayer.getDescendantCoordRelativeToSelf(cellLayout, rightBottom); 271 272 target.set(leftTop[0], leftTop[1], rightBottom[0], rightBottom[1]); 273 return target; 274 } 275 276 protected boolean isLauncherInitialized() { 277 return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null 278 || LauncherAppState.getInstance(mContext).getModel().isModelLoaded(); 279 } 280 281 protected Activity getCurrentActivity() { 282 return Launcher.ACTIVITY_TRACKER.getCreatedActivity(); 283 } 284 285 /** 286 * Returns the result by getting a Launcher property on UI thread 287 */ 288 public static <T> Bundle getLauncherUIProperty( 289 BundleSetter<T> bundleSetter, Function<Launcher, T> provider) { 290 return getUIProperty(bundleSetter, provider, Launcher.ACTIVITY_TRACKER::getCreatedActivity); 291 } 292 293 /** 294 * Returns the result by getting a generic property on UI thread 295 */ 296 private static <S, T> Bundle getUIProperty( 297 BundleSetter<T> bundleSetter, Function<S, T> provider, Supplier<S> targetSupplier) { 298 return getFromExecutorSync(MAIN_EXECUTOR, () -> { 299 S target = targetSupplier.get(); 300 if (target == null) { 301 return null; 302 } 303 T value = provider.apply(target); 304 Bundle response = new Bundle(); 305 bundleSetter.set(response, TestProtocol.TEST_INFO_RESPONSE_FIELD, value); 306 return response; 307 }); 308 } 309 310 /** 311 * Executes the callback on the executor and waits for the result 312 */ 313 protected static <T> T getFromExecutorSync(ExecutorService executor, Callable<T> callback) { 314 try { 315 return executor.submit(callback).get(); 316 } catch (ExecutionException | InterruptedException e) { 317 throw new RuntimeException(e); 318 } 319 } 320 321 /** 322 * Generic interface for setting a fiend in bundle 323 * 324 * @param <T> the type of value being set 325 */ 326 public interface BundleSetter<T> { 327 328 /** 329 * Sets any generic property to the bundle 330 */ 331 void set(Bundle b, String key, T value); 332 } 333 } 334