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