• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Flags.enableFallbackOverviewInWindow;
19 import static com.android.launcher3.Flags.enableGridOnlyOverview;
20 import static com.android.launcher3.Flags.enableLauncherOverviewInWindow;
21 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
22 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
23 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
24 import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
25 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
26 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
27 
28 import android.app.Activity;
29 import android.app.Application;
30 import android.content.Context;
31 import android.content.res.Resources;
32 import android.graphics.Insets;
33 import android.graphics.Point;
34 import android.graphics.Rect;
35 import android.os.Binder;
36 import android.os.Bundle;
37 import android.system.Os;
38 import android.view.WindowInsets;
39 
40 import androidx.annotation.Keep;
41 import androidx.annotation.Nullable;
42 import androidx.core.view.WindowInsetsCompat;
43 
44 import com.android.launcher3.BubbleTextView;
45 import com.android.launcher3.CellLayout;
46 import com.android.launcher3.DeviceProfile;
47 import com.android.launcher3.Hotseat;
48 import com.android.launcher3.InvariantDeviceProfile;
49 import com.android.launcher3.Launcher;
50 import com.android.launcher3.LauncherAppState;
51 import com.android.launcher3.LauncherModel;
52 import com.android.launcher3.LauncherState;
53 import com.android.launcher3.R;
54 import com.android.launcher3.ShortcutAndWidgetContainer;
55 import com.android.launcher3.Workspace;
56 import com.android.launcher3.dragndrop.DragLayer;
57 import com.android.launcher3.icons.ClockDrawableWrapper;
58 import com.android.launcher3.testing.shared.TestProtocol;
59 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
60 import com.android.launcher3.util.DisplayController;
61 import com.android.launcher3.util.ResourceBasedOverride;
62 import com.android.launcher3.widget.picker.WidgetsFullSheet;
63 
64 import java.util.ArrayList;
65 import java.util.Collection;
66 import java.util.Collections;
67 import java.util.Set;
68 import java.util.WeakHashMap;
69 import java.util.concurrent.Callable;
70 import java.util.concurrent.CountDownLatch;
71 import java.util.concurrent.ExecutionException;
72 import java.util.concurrent.ExecutorService;
73 import java.util.concurrent.TimeUnit;
74 import java.util.function.Function;
75 import java.util.function.Supplier;
76 
77 /**
78  * Class to handle requests from tests
79  */
80 public class TestInformationHandler implements ResourceBasedOverride {
81 
newInstance(Context context)82     public static TestInformationHandler newInstance(Context context) {
83         return Overrides.getObject(TestInformationHandler.class,
84                 context, R.string.test_information_handler_class);
85     }
86 
87     private static Collection<String> sEvents;
88     private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
89     private static final Set<Activity> sActivities =
90             Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
91     private static int sActivitiesCreatedCount = 0;
92 
93     protected Context mContext;
94     protected DeviceProfile mDeviceProfile;
95 
init(Context context)96     public void init(Context context) {
97         mContext = context;
98         mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context);
99         if (sActivityLifecycleCallbacks == null) {
100             sActivityLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
101                 @Override
102                 public void onActivityCreated(Activity activity, Bundle bundle) {
103                     sActivities.add(activity);
104                     ++sActivitiesCreatedCount;
105                 }
106             };
107             ((Application) context.getApplicationContext())
108                     .registerActivityLifecycleCallbacks(sActivityLifecycleCallbacks);
109         }
110     }
111 
112     /**
113      * handle a request and return result Bundle.
114      *
115      * @param method request name.
116      * @param arg    optional single string argument.
117      * @param extra  extra request payload.
118      */
call(String method, String arg, @Nullable Bundle extra)119     public Bundle call(String method, String arg, @Nullable Bundle extra) {
120         final Bundle response = new Bundle();
121         if (extra != null && extra.getClassLoader() == null) {
122             extra.setClassLoader(getClass().getClassLoader());
123         }
124         switch (method) {
125             case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
126                 return getLauncherUIProperty(Bundle::putInt, l -> {
127                     final float progress = LauncherState.NORMAL.getVerticalProgress(l)
128                             - LauncherState.ALL_APPS.getVerticalProgress(l);
129                     final float distance = l.getAllAppsController().getShiftRange() * progress;
130                     return (int) distance;
131                 });
132             }
133 
134             case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
135                 return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true);
136             }
137 
138             case TestProtocol.REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED: {
139                 final Bundle bundle = getLauncherUIProperty(Bundle::putBoolean, l -> l.isStarted());
140                 if (bundle != null) return bundle;
141 
142                 // If Launcher activity wasn't created, it's not started.
143                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false);
144                 return response;
145             }
146 
147             case TestProtocol.REQUEST_FREEZE_APP_LIST:
148                 return getLauncherUIProperty(Bundle::putBoolean, l -> {
149                     l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
150                     return true;
151                 });
152             case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
153                 return getLauncherUIProperty(Bundle::putBoolean, l -> {
154                     l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
155                     return true;
156                 });
157 
158             case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
159                 return getLauncherUIProperty(Bundle::putInt,
160                         l -> l.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset());
161             }
162 
163             case TestProtocol.REQUEST_WIDGETS_SCROLL_Y: {
164                 return getLauncherUIProperty(Bundle::putInt,
165                         l -> WidgetsFullSheet.getWidgetsView(l).computeVerticalScrollOffset());
166             }
167 
168             case TestProtocol.REQUEST_TARGET_INSETS: {
169                 return getUIProperty(Bundle::putParcelable, insets -> Insets.max(
170                         insets.getSystemGestureInsets(),
171                         insets.getSystemWindowInsets()), this::getWindowInsets);
172             }
173 
174             case TestProtocol.REQUEST_WINDOW_INSETS: {
175                 return getUIProperty(Bundle::putParcelable,
176                         WindowInsets::getSystemWindowInsets, this::getWindowInsets);
177             }
178 
179             case TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT: {
180                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
181                         mDeviceProfile.cellLayoutBorderSpacePx.y);
182                 return response;
183             }
184 
185             case TestProtocol.REQUEST_SYSTEM_GESTURE_REGION: {
186                 return getUIProperty(Bundle::putParcelable, windowInsets -> {
187                     WindowInsetsCompat insets =
188                             WindowInsetsCompat.toWindowInsetsCompat(windowInsets);
189                     return insets.getInsets(WindowInsetsCompat.Type.ime()
190                             | WindowInsetsCompat.Type.systemGestures())
191                             .toPlatformInsets();
192                 }, this::getWindowInsets);
193             }
194 
195             case TestProtocol.REQUEST_ICON_HEIGHT: {
196                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
197                         mDeviceProfile.allAppsCellHeightPx);
198                 return response;
199             }
200 
201             case TestProtocol.REQUEST_MOCK_SENSOR_ROTATION:
202                 TestProtocol.sDisableSensorRotation = true;
203                 return response;
204 
205             case TestProtocol.REQUEST_IS_TABLET:
206                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, mDeviceProfile.isTablet);
207                 return response;
208             case TestProtocol.REQUEST_IS_PREDICTIVE_BACK_SWIPE_ENABLED:
209                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
210                         mDeviceProfile.isPredictiveBackSwipe);
211                 return response;
212             case TestProtocol.REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION:
213                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
214                         ENABLE_TASKBAR_NAVBAR_UNIFICATION);
215                 return response;
216 
217             case TestProtocol.REQUEST_TASKBAR_SHOWN_ON_HOME:
218                 response.putBoolean(TEST_INFO_RESPONSE_FIELD,
219                         DisplayController.showLockedTaskbarOnHome(mContext));
220                 return response;
221             case TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS:
222                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
223                         mDeviceProfile.numShownAllAppsColumns);
224                 return response;
225 
226             case TestProtocol.REQUEST_IS_TRANSIENT_TASKBAR:
227                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
228                         DisplayController.isTransientTaskbar(mContext));
229                 return response;
230 
231             case TestProtocol.REQUEST_IS_TWO_PANELS:
232                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
233                         FOLDABLE_SINGLE_PAGE.get() ? false : mDeviceProfile.isTwoPanels);
234                 return response;
235 
236             case TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS:
237                 response.putBoolean(
238                         TestProtocol.TEST_INFO_RESPONSE_FIELD, TestLogging.sHadEventsNotFromTest);
239                 return response;
240 
241             case TestProtocol.REQUEST_START_DRAG_THRESHOLD: {
242                 final Resources resources = mContext.getResources();
243                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
244                         resources.getDimensionPixelSize(R.dimen.deep_shortcuts_start_drag_threshold)
245                                 + resources.getDimensionPixelSize(R.dimen.pre_drag_view_scale));
246                 return response;
247             }
248 
249             case TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE:
250                 response.putBoolean(TEST_INFO_RESPONSE_FIELD,
251                         Launcher.ACTIVITY_TRACKER.getCreatedContext().isSplitSelectionActive());
252                 return response;
253 
254             case TestProtocol.REQUEST_ENABLE_ROTATION:
255                 MAIN_EXECUTOR.submit(() ->
256                         Launcher.ACTIVITY_TRACKER.getCreatedContext().getRotationHelper()
257                                 .forceAllowRotationForTesting(Boolean.parseBoolean(arg)));
258                 return response;
259 
260             case TestProtocol.REQUEST_WORKSPACE_CELL_LAYOUT_SIZE:
261                 return getLauncherUIProperty(Bundle::putIntArray, launcher -> {
262                     final Workspace<?> workspace = launcher.getWorkspace();
263                     final int screenId = workspace.getScreenIdForPageIndex(
264                             workspace.getCurrentPage());
265                     final CellLayout cellLayout = workspace.getScreenWithId(screenId);
266                     return new int[]{cellLayout.getCountX(), cellLayout.getCountY()};
267                 });
268 
269             case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER: {
270                 Rect cellPos = extra.getParcelable(TestProtocol.TEST_INFO_PARAM_CELL_SPAN);
271                 return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
272                     final Workspace<?> workspace = launcher.getWorkspace();
273                     // TODO(b/216387249): allow caller selecting different pages.
274                     CellLayout cellLayout = (CellLayout) workspace.getPageAt(
275                             workspace.getCurrentPage());
276                     final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
277                             cellLayout, cellPos.left, cellPos.top, cellPos.width(),
278                             cellPos.height());
279                     return new Point(cellRect.centerX(), cellRect.centerY());
280                 });
281             }
282 
283             case TestProtocol.REQUEST_WORKSPACE_COLUMNS_ROWS: {
284                 InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
285                 return getLauncherUIProperty(Bundle::putParcelable, launcher -> new Point(
286                         idp.getDeviceProfile(mContext).getPanelCount() * idp.numColumns,
287                         idp.numRows
288                 ));
289             }
290 
291             case TestProtocol.REQUEST_WORKSPACE_CURRENT_PAGE_INDEX: {
292                 return getLauncherUIProperty(Bundle::putInt,
293                         launcher -> launcher.getWorkspace().getCurrentPage());
294             }
295 
296             case TestProtocol.REQUEST_HOTSEAT_CELL_CENTER: {
297                 int cellIndex = extra.getInt(TestProtocol.TEST_INFO_PARAM_INDEX);
298                 return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
299                     final Hotseat hotseat = launcher.getHotseat();
300                     final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
301                             hotseat, cellIndex, /* cellY= */ 0,
302                             /* spanX= */ 1, /* spanY= */ 1);
303                     // TODO(b/234322284): return the real center point.
304                     return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3,
305                             cellRect.top + (cellRect.bottom - cellRect.top) / 3);
306                 });
307             }
308 
309             case TestProtocol.REQUEST_HAS_TIS: {
310                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false);
311                 return response;
312             }
313 
314             case TestProtocol.REQUEST_ALL_APPS_TOP_PADDING: {
315                 return getLauncherUIProperty(Bundle::putInt,
316                         l -> l.getAppsView().getActiveRecyclerView().getClipBounds().top);
317             }
318 
319             case TestProtocol.REQUEST_ALL_APPS_BOTTOM_PADDING: {
320                 return getLauncherUIProperty(Bundle::putInt,
321                         l -> l.getAppsView().getBottom()
322                                 - l.getAppsView().getActiveRecyclerView().getBottom()
323                                 + l.getAppsView().getActiveRecyclerView().getPaddingBottom());
324             }
325 
326             case TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW: {
327                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
328                         enableGridOnlyOverview());
329                 return response;
330             }
331 
332             case TestProtocol.REQUEST_IS_RECENTS_WINDOW_ENABLED: {
333                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
334                         enableLauncherOverviewInWindow() || enableFallbackOverviewInWindow());
335                 return response;
336             }
337 
338             case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
339                 return getLauncherUIProperty(Bundle::putInt,
340                         l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
341             }
342 
343             case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
344                 TestProtocol.sDebugTracing = true;
345                 ClockDrawableWrapper.sRunningInTest = true;
346                 return response;
347 
348             case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
349                 TestProtocol.sDebugTracing = false;
350                 ClockDrawableWrapper.sRunningInTest = false;
351                 return response;
352 
353             case TestProtocol.REQUEST_PID: {
354                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
355                 return response;
356             }
357 
358             case TestProtocol.REQUEST_FORCE_GC: {
359                 runGcAndFinalizersSync();
360                 return response;
361             }
362 
363             case TestProtocol.REQUEST_START_EVENT_LOGGING: {
364                 sEvents = new ArrayList<>();
365                 TestLogging.setEventConsumer(
366                         (sequence, event) -> {
367                             final Collection<String> events = sEvents;
368                             if (events != null) {
369                                 synchronized (events) {
370                                     events.add(sequence + '/' + event);
371                                 }
372                             }
373                         });
374                 return response;
375             }
376 
377             case TestProtocol.REQUEST_STOP_EVENT_LOGGING: {
378                 TestLogging.setEventConsumer(null);
379                 sEvents = null;
380                 return response;
381             }
382 
383             case TestProtocol.REQUEST_GET_TEST_EVENTS: {
384                 if (sEvents == null) {
385                     // sEvents can be null if Launcher died and restarted after
386                     // REQUEST_START_EVENT_LOGGING.
387                     return response;
388                 }
389 
390                 synchronized (sEvents) {
391                     response.putStringArrayList(
392                             TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
393                 }
394                 return response;
395             }
396 
397             case TestProtocol.REQUEST_REINITIALIZE_DATA: {
398                 final long identity = Binder.clearCallingIdentity();
399                 try {
400                     MODEL_EXECUTOR.execute(() -> {
401                         LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
402                         model.getModelDbController().createEmptyDB();
403                         MAIN_EXECUTOR.execute(model::forceReload);
404                     });
405                     return response;
406                 } finally {
407                     Binder.restoreCallingIdentity(identity);
408                 }
409             }
410 
411             case TestProtocol.REQUEST_CLEAR_DATA: {
412                 final long identity = Binder.clearCallingIdentity();
413                 try {
414                     MODEL_EXECUTOR.execute(() -> {
415                         LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
416                         model.getModelDbController().createEmptyDB();
417                         model.getModelDbController().clearEmptyDbFlag();
418                         MAIN_EXECUTOR.execute(model::forceReload);
419                     });
420                     return response;
421                 } finally {
422                     Binder.restoreCallingIdentity(identity);
423                 }
424             }
425 
426             case TestProtocol.REQUEST_HOTSEAT_ICON_NAMES: {
427                 return getLauncherUIProperty(Bundle::putStringArrayList, l -> {
428                     ShortcutAndWidgetContainer hotseatIconsContainer =
429                             l.getHotseat().getShortcutsAndWidgets();
430                     ArrayList<String> hotseatIconNames = new ArrayList<>();
431 
432                     for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
433                         // Use unchecked cast to catch changes in hotseat layout
434                         BubbleTextView icon = (BubbleTextView) hotseatIconsContainer.getChildAt(i);
435                         hotseatIconNames.add((String) icon.getText());
436                     }
437 
438                     return hotseatIconNames;
439                 });
440             }
441 
442             case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
443                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
444                 return response;
445             }
446 
447             case TestProtocol.REQUEST_GET_ACTIVITIES: {
448                 response.putStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD,
449                         sActivities.stream().map(
450                                         a -> a.getClass().getSimpleName() + " ("
451                                                 + (a.isDestroyed() ? "destroyed" : "current") + ")")
452                                 .toArray(String[]::new));
453                 return response;
454             }
455 
456             case TestProtocol.REQUEST_MODEL_QUEUE_CLEARED:
457                 return getFromExecutorSync(MODEL_EXECUTOR, Bundle::new);
458 
459             default:
460                 return null;
461         }
462     }
463 
464     private static Rect getDescendantRectRelativeToDragLayerForCell(Launcher launcher,
465             CellLayout cellLayout, int cellX, int cellY, int spanX, int spanY) {
466         final DragLayer dragLayer = launcher.getDragLayer();
467         final Rect target = new Rect();
468 
469         cellLayout.cellToRect(cellX, cellY, spanX, spanY, target);
470         int[] leftTop = {target.left, target.top};
471         int[] rightBottom = {target.right, target.bottom};
472         dragLayer.getDescendantCoordRelativeToSelf(cellLayout, leftTop);
473         dragLayer.getDescendantCoordRelativeToSelf(cellLayout, rightBottom);
474 
475         target.set(leftTop[0], leftTop[1], rightBottom[0], rightBottom[1]);
476         return target;
477     }
478 
479     protected boolean isLauncherInitialized() {
480         return Launcher.ACTIVITY_TRACKER.getCreatedContext() == null
481                 || LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
482     }
483 
484     protected WindowInsets getWindowInsets(){
485         return Launcher.ACTIVITY_TRACKER.getCreatedContext().getWindow().getDecorView()
486                 .getRootWindowInsets();
487     }
488 
489     /**
490      * Returns the result by getting a Launcher property on UI thread
491      */
492     public static <T> Bundle getLauncherUIProperty(
493             BundleSetter<T> bundleSetter, Function<Launcher, T> provider) {
494         return getUIProperty(bundleSetter, provider, Launcher.ACTIVITY_TRACKER::getCreatedContext);
495     }
496 
497     /**
498      * Returns the result by getting a generic property on UI thread
499      */
500     protected static <S, T> Bundle getUIProperty(
501             BundleSetter<T> bundleSetter, Function<S, T> provider, Supplier<S> targetSupplier) {
502         return getFromExecutorSync(MAIN_EXECUTOR, () -> {
503             S target = targetSupplier.get();
504             if (target == null) {
505                 return null;
506             }
507             T value = provider.apply(target);
508 
509             Bundle response = new Bundle();
510             bundleSetter.set(response, TestProtocol.TEST_INFO_RESPONSE_FIELD, value);
511             return response;
512         });
513     }
514 
515     /**
516      * Executes the callback on the executor and waits for the result
517      */
518     protected static <T> T getFromExecutorSync(ExecutorService executor, Callable<T> callback) {
519         try {
520             return executor.submit(callback).get();
521         } catch (ExecutionException | InterruptedException e) {
522             throw new RuntimeException(e);
523         }
524     }
525 
526     /**
527      * Generic interface for setting a fiend in bundle
528      *
529      * @param <T> the type of value being set
530      */
531     public interface BundleSetter<T> {
532 
533         /**
534          * Sets any generic property to the bundle
535          */
536         void set(Bundle b, String key, T value);
537     }
538 
539 
540     private static void runGcAndFinalizersSync() {
541         Runtime.getRuntime().gc();
542         Runtime.getRuntime().runFinalization();
543 
544         final CountDownLatch fence = new CountDownLatch(1);
545         createFinalizationObserver(fence);
546         try {
547             do {
548                 Runtime.getRuntime().gc();
549                 Runtime.getRuntime().runFinalization();
550             } while (!fence.await(100, TimeUnit.MILLISECONDS));
551         } catch (InterruptedException ex) {
552             throw new RuntimeException(ex);
553         }
554     }
555 
556     // Create the observer in the scope of a method to minimize the chance that
557     // it remains live in a DEX/machine register at the point of the fence guard.
558     // This must be kept to avoid R8 inlining it.
559     @Keep
560     private static void createFinalizationObserver(CountDownLatch fence) {
561         new Object() {
562             @Override
563             protected void finalize() throws Throwable {
564                 try {
565                     fence.countDown();
566                 } finally {
567                     super.finalize();
568                 }
569             }
570         };
571     }
572 }
573