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