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