1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.annotation.SuppressLint; 8 import android.app.Activity; 9 import android.app.Application; 10 import android.app.Application.ActivityLifecycleCallbacks; 11 import android.os.Bundle; 12 import android.view.Window; 13 14 import androidx.annotation.AnyThread; 15 import androidx.annotation.MainThread; 16 import androidx.annotation.Nullable; 17 import androidx.annotation.VisibleForTesting; 18 19 import org.chromium.base.annotations.CalledByNative; 20 import org.chromium.base.annotations.JNINamespace; 21 import org.chromium.base.annotations.NativeMethods; 22 23 import java.lang.reflect.Field; 24 import java.lang.reflect.InvocationHandler; 25 import java.lang.reflect.InvocationTargetException; 26 import java.lang.reflect.Method; 27 import java.lang.reflect.Proxy; 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.HashMap; 31 import java.util.List; 32 import java.util.Map; 33 34 import javax.annotation.concurrent.GuardedBy; 35 36 /** 37 * Provides information about the current activity's status, and a way 38 * to register / unregister listeners for state changes. 39 * TODO(https://crbug.com/470582): ApplicationStatus will not work on WebView/WebLayer, and 40 * should be moved out of base and into //chrome. It should not be relied upon for //components. 41 */ 42 @JNINamespace("base::android") 43 public class ApplicationStatus { 44 private static final String TOOLBAR_CALLBACK_WRAPPER_CLASS = 45 "androidx.appcompat.app.ToolbarActionBar$ToolbarCallbackWrapper"; 46 47 private static class ActivityInfo { 48 private int mStatus = ActivityState.DESTROYED; 49 private ObserverList<ActivityStateListener> mListeners = new ObserverList<>(); 50 51 /** 52 * @return The current {@link ActivityState} of the activity. 53 */ 54 @ActivityState getStatus()55 public int getStatus() { 56 return mStatus; 57 } 58 59 /** 60 * @param status The new {@link ActivityState} of the activity. 61 */ setStatus(@ctivityState int status)62 public void setStatus(@ActivityState int status) { 63 mStatus = status; 64 } 65 66 /** 67 * @return A list of {@link ActivityStateListener}s listening to this activity. 68 */ getListeners()69 public ObserverList<ActivityStateListener> getListeners() { 70 return mListeners; 71 } 72 } 73 74 /** 75 * A map of which observers listen to state changes from which {@link Activity}. 76 */ 77 private static final Map<Activity, ActivityInfo> sActivityInfo = 78 Collections.synchronizedMap(new HashMap<Activity, ActivityInfo>()); 79 80 @SuppressLint("SupportAnnotationUsage") 81 @ApplicationState 82 @GuardedBy("sActivityInfo") 83 // The getStateForApplication() historically returned ApplicationState.HAS_DESTROYED_ACTIVITIES 84 // when no activity has been observed. 85 private static int sCurrentApplicationState = ApplicationState.UNKNOWN; 86 87 /** Last activity that was shown (or null if none or it was destroyed). */ 88 @SuppressLint("StaticFieldLeak") 89 private static Activity sActivity; 90 91 /** A lazily initialized listener that forwards application state changes to native. */ 92 private static ApplicationStateListener sNativeApplicationStateListener; 93 94 /** 95 * A list of observers to be notified when any {@link Activity} has a state change. 96 */ 97 private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners = 98 new ObserverList<>(); 99 100 /** 101 * A list of observers to be notified when the visibility state of this {@link Application} 102 * changes. See {@link #getStateForApplication()}. 103 */ 104 private static final ObserverList<ApplicationStateListener> sApplicationStateListeners = 105 new ObserverList<>(); 106 107 /** 108 * A list of observers to be notified when the window focus changes. 109 * See {@link #registerWindowFocusChangedListener}. 110 */ 111 private static final ObserverList<WindowFocusChangedListener> sWindowFocusListeners = 112 new ObserverList<>(); 113 114 /** 115 * Interface to be implemented by listeners. 116 */ 117 public interface ApplicationStateListener { 118 /** 119 * Called when the application's state changes. 120 * @param newState The application state. 121 */ onApplicationStateChange(@pplicationState int newState)122 void onApplicationStateChange(@ApplicationState int newState); 123 } 124 125 /** 126 * Interface to be implemented by listeners. 127 */ 128 public interface ActivityStateListener { 129 /** 130 * Called when the activity's state changes. 131 * @param activity The activity that had a state change. 132 * @param newState New activity state. 133 */ onActivityStateChange(Activity activity, @ActivityState int newState)134 void onActivityStateChange(Activity activity, @ActivityState int newState); 135 } 136 137 /** 138 * Interface to be implemented by listeners for window focus events. 139 */ 140 public interface WindowFocusChangedListener { 141 /** 142 * Called when the window focus changes for {@code activity}. 143 * @param activity The {@link Activity} that has a window focus changed event. 144 * @param hasFocus Whether or not {@code activity} gained or lost focus. 145 */ onWindowFocusChanged(Activity activity, boolean hasFocus)146 public void onWindowFocusChanged(Activity activity, boolean hasFocus); 147 } 148 ApplicationStatus()149 private ApplicationStatus() {} 150 151 /** 152 * Registers a listener to receive window focus updates on activities in this application. 153 * @param listener Listener to receive window focus events. 154 */ 155 @MainThread registerWindowFocusChangedListener(WindowFocusChangedListener listener)156 public static void registerWindowFocusChangedListener(WindowFocusChangedListener listener) { 157 assert isInitialized(); 158 sWindowFocusListeners.addObserver(listener); 159 } 160 161 /** 162 * Unregisters a listener from receiving window focus updates on activities in this application. 163 * @param listener Listener that doesn't want to receive window focus events. 164 */ 165 @MainThread unregisterWindowFocusChangedListener(WindowFocusChangedListener listener)166 public static void unregisterWindowFocusChangedListener(WindowFocusChangedListener listener) { 167 sWindowFocusListeners.removeObserver(listener); 168 } 169 170 /** 171 * Intercepts calls to an existing Window.Callback. Most invocations are passed on directly 172 * to the composed Window.Callback but enables intercepting/manipulating others. 173 * 174 * This is used to relay window focus changes throughout the app and remedy a bug in the 175 * appcompat library. 176 */ 177 @VisibleForTesting 178 static class WindowCallbackProxy implements InvocationHandler { 179 private final Window.Callback mCallback; 180 private final Activity mActivity; 181 WindowCallbackProxy(Activity activity, Window.Callback callback)182 public WindowCallbackProxy(Activity activity, Window.Callback callback) { 183 mCallback = callback; 184 mActivity = activity; 185 } 186 187 @Override invoke(Object proxy, Method method, Object[] args)188 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 189 if (method.getName().equals("onWindowFocusChanged") && args.length == 1 190 && args[0] instanceof Boolean) { 191 onWindowFocusChanged((boolean) args[0]); 192 return null; 193 } else { 194 try { 195 return method.invoke(mCallback, args); 196 } catch (InvocationTargetException e) { 197 // Special-case for when a method is not defined on the underlying 198 // Window.Callback object. Because we're using a Proxy to forward all method 199 // calls, this breaks the Android framework's handling for apps built against 200 // an older SDK. The framework expects an AbstractMethodError but due to 201 // reflection it becomes wrapped inside an InvocationTargetException. Undo the 202 // wrapping to signal the framework accordingly. 203 if (e.getCause() instanceof AbstractMethodError) { 204 throw e.getCause(); 205 } 206 throw e; 207 } 208 } 209 } 210 onWindowFocusChanged(boolean hasFocus)211 public void onWindowFocusChanged(boolean hasFocus) { 212 mCallback.onWindowFocusChanged(hasFocus); 213 214 for (WindowFocusChangedListener listener : sWindowFocusListeners) { 215 listener.onWindowFocusChanged(mActivity, hasFocus); 216 } 217 } 218 } 219 isInitialized()220 public static boolean isInitialized() { 221 synchronized (sActivityInfo) { 222 return sCurrentApplicationState != ApplicationState.UNKNOWN; 223 } 224 } 225 226 /** 227 * Initializes the activity status for a specified application. 228 * 229 * @param application The application whose status you wish to monitor. 230 */ 231 @MainThread initialize(Application application)232 public static void initialize(Application application) { 233 assert !isInitialized(); 234 synchronized (sActivityInfo) { 235 sCurrentApplicationState = ApplicationState.HAS_DESTROYED_ACTIVITIES; 236 } 237 238 registerWindowFocusChangedListener(new WindowFocusChangedListener() { 239 @Override 240 public void onWindowFocusChanged(Activity activity, boolean hasFocus) { 241 if (!hasFocus || activity == sActivity) return; 242 243 int state = getStateForActivity(activity); 244 245 if (state != ActivityState.DESTROYED && state != ActivityState.STOPPED) { 246 sActivity = activity; 247 } 248 249 // TODO(dtrainor): Notify of active activity change? 250 } 251 }); 252 253 application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { 254 @Override 255 public void onActivityCreated(final Activity activity, Bundle savedInstanceState) { 256 onStateChange(activity, ActivityState.CREATED); 257 Window.Callback callback = activity.getWindow().getCallback(); 258 activity.getWindow().setCallback(createWindowCallbackProxy(activity, callback)); 259 } 260 261 @Override 262 public void onActivityDestroyed(Activity activity) { 263 onStateChange(activity, ActivityState.DESTROYED); 264 checkCallback(activity); 265 } 266 267 @Override 268 public void onActivityPaused(Activity activity) { 269 onStateChange(activity, ActivityState.PAUSED); 270 checkCallback(activity); 271 } 272 273 @Override 274 public void onActivityResumed(Activity activity) { 275 onStateChange(activity, ActivityState.RESUMED); 276 checkCallback(activity); 277 } 278 279 @Override 280 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 281 checkCallback(activity); 282 } 283 284 @Override 285 public void onActivityStarted(Activity activity) { 286 onStateChange(activity, ActivityState.STARTED); 287 checkCallback(activity); 288 } 289 290 @Override 291 public void onActivityStopped(Activity activity) { 292 onStateChange(activity, ActivityState.STOPPED); 293 checkCallback(activity); 294 } 295 296 private void checkCallback(Activity activity) { 297 if (BuildConfig.DCHECK_IS_ON) { 298 assert reachesWindowCallback(activity.getWindow().getCallback()); 299 } 300 } 301 }); 302 } 303 304 @VisibleForTesting createWindowCallbackProxy(Activity activity, Window.Callback callback)305 static Window.Callback createWindowCallbackProxy(Activity activity, Window.Callback callback) { 306 return (Window.Callback) Proxy.newProxyInstance(Window.Callback.class.getClassLoader(), 307 new Class[] {Window.Callback.class}, 308 new ApplicationStatus.WindowCallbackProxy(activity, callback)); 309 } 310 311 /** 312 * Tries to trace down to our WindowCallbackProxy from the given callback. 313 * Since the callback can be overwritten by embedder code we try to ensure 314 * that there at least seem to be a reference back to our callback by 315 * checking the declared fields of the given callback using reflection. 316 */ 317 @VisibleForTesting reachesWindowCallback(@ullable Window.Callback callback)318 static boolean reachesWindowCallback(@Nullable Window.Callback callback) { 319 if (callback == null) return false; 320 if (callback.getClass().getName().equals(TOOLBAR_CALLBACK_WRAPPER_CLASS)) { 321 // We're actually not going to get called, see AndroidX report here: 322 // https://issuetracker.google.com/issues/155165145. 323 // But this was accepted in the old code as well so mimic that until 324 // AndroidX is fixed and updated. 325 return true; 326 } 327 if (Proxy.isProxyClass(callback.getClass())) { 328 return Proxy.getInvocationHandler(callback) 329 instanceof ApplicationStatus.WindowCallbackProxy; 330 } 331 for (Class<?> c = callback.getClass(); c != Object.class; c = c.getSuperclass()) { 332 for (Field f : c.getDeclaredFields()) { 333 if (f.getType().isAssignableFrom(Window.Callback.class)) { 334 boolean isAccessible = f.isAccessible(); 335 f.setAccessible(true); 336 Window.Callback fieldCb; 337 try { 338 fieldCb = (Window.Callback) f.get(callback); 339 } catch (IllegalAccessException ex) { 340 continue; 341 } finally { 342 f.setAccessible(isAccessible); 343 } 344 if (reachesWindowCallback(fieldCb)) { 345 return true; 346 } 347 } 348 } 349 } 350 return false; 351 } 352 353 /** 354 * Must be called by the main activity when it changes state. 355 * 356 * @param activity Current activity. 357 * @param newState New state value. 358 */ onStateChange(Activity activity, @ActivityState int newState)359 private static void onStateChange(Activity activity, @ActivityState int newState) { 360 if (activity == null) throw new IllegalArgumentException("null activity is not supported"); 361 362 if (sActivity == null 363 || newState == ActivityState.CREATED 364 || newState == ActivityState.RESUMED 365 || newState == ActivityState.STARTED) { 366 sActivity = activity; 367 } 368 369 int oldApplicationState = getStateForApplication(); 370 ActivityInfo info; 371 372 synchronized (sActivityInfo) { 373 if (newState == ActivityState.CREATED) { 374 assert !sActivityInfo.containsKey(activity); 375 sActivityInfo.put(activity, new ActivityInfo()); 376 } 377 378 info = sActivityInfo.get(activity); 379 info.setStatus(newState); 380 381 // Remove before calling listeners so that isEveryActivityDestroyed() returns false when 382 // this was the last activity. 383 if (newState == ActivityState.DESTROYED) { 384 sActivityInfo.remove(activity); 385 if (activity == sActivity) sActivity = null; 386 } 387 388 sCurrentApplicationState = determineApplicationStateLocked(); 389 } 390 391 // Notify all state observers that are specifically listening to this activity. 392 for (ActivityStateListener listener : info.getListeners()) { 393 listener.onActivityStateChange(activity, newState); 394 } 395 396 // Notify all state observers that are listening globally for all activity state 397 // changes. 398 for (ActivityStateListener listener : sGeneralActivityStateListeners) { 399 listener.onActivityStateChange(activity, newState); 400 } 401 402 int applicationState = getStateForApplication(); 403 if (applicationState != oldApplicationState) { 404 for (ApplicationStateListener listener : sApplicationStateListeners) { 405 listener.onApplicationStateChange(applicationState); 406 } 407 } 408 } 409 410 /** 411 * Testing method to update the state of the specified activity. 412 */ 413 @VisibleForTesting 414 @MainThread onStateChangeForTesting(Activity activity, int newState)415 public static void onStateChangeForTesting(Activity activity, int newState) { 416 onStateChange(activity, newState); 417 } 418 419 /** 420 * @return The most recent focused {@link Activity} tracked by this class. Being focused means 421 * out of all the activities tracked here, it has most recently gained window focus. 422 */ 423 @MainThread getLastTrackedFocusedActivity()424 public static Activity getLastTrackedFocusedActivity() { 425 return sActivity; 426 } 427 428 /** 429 * @return A {@link List} of all non-destroyed {@link Activity}s. 430 */ 431 @AnyThread getRunningActivities()432 public static List<Activity> getRunningActivities() { 433 assert isInitialized(); 434 synchronized (sActivityInfo) { 435 return new ArrayList<>(sActivityInfo.keySet()); 436 } 437 } 438 439 /** 440 * Query the state for a given activity. If the activity is not being tracked, this will 441 * return {@link ActivityState#DESTROYED}. 442 * 443 * <p> 444 * Please note that Chrome can have multiple activities running simultaneously. Please also 445 * look at {@link #getStateForApplication()} for more details. 446 * 447 * <p> 448 * When relying on this method, be familiar with the expected life cycle state 449 * transitions: 450 * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle"> 451 * Activity Lifecycle 452 * </a> 453 * 454 * <p> 455 * During activity transitions (activity B launching in front of activity A), A will completely 456 * paused before the creation of activity B begins. 457 * 458 * <p> 459 * A basic flow for activity A starting, followed by activity B being opened and then closed: 460 * <ul> 461 * <li> -- Starting Activity A -- 462 * <li> Activity A - ActivityState.CREATED 463 * <li> Activity A - ActivityState.STARTED 464 * <li> Activity A - ActivityState.RESUMED 465 * <li> -- Starting Activity B -- 466 * <li> Activity A - ActivityState.PAUSED 467 * <li> Activity B - ActivityState.CREATED 468 * <li> Activity B - ActivityState.STARTED 469 * <li> Activity B - ActivityState.RESUMED 470 * <li> Activity A - ActivityState.STOPPED 471 * <li> -- Closing Activity B, Activity A regaining focus -- 472 * <li> Activity B - ActivityState.PAUSED 473 * <li> Activity A - ActivityState.STARTED 474 * <li> Activity A - ActivityState.RESUMED 475 * <li> Activity B - ActivityState.STOPPED 476 * <li> Activity B - ActivityState.DESTROYED 477 * </ul> 478 * 479 * @param activity The activity whose state is to be returned. 480 * @return The state of the specified activity (see {@link ActivityState}). 481 */ 482 @ActivityState 483 @AnyThread getStateForActivity(@ullable Activity activity)484 public static int getStateForActivity(@Nullable Activity activity) { 485 assert isInitialized(); 486 if (activity == null) return ActivityState.DESTROYED; 487 ActivityInfo info = sActivityInfo.get(activity); 488 return info != null ? info.getStatus() : ActivityState.DESTROYED; 489 } 490 491 /** 492 * @return The state of the application (see {@link ApplicationState}). 493 */ 494 @AnyThread 495 @ApplicationState 496 @CalledByNative getStateForApplication()497 public static int getStateForApplication() { 498 synchronized (sActivityInfo) { 499 return sCurrentApplicationState; 500 } 501 } 502 503 /** 504 * Checks whether or not any Activity in this Application is visible to the user. Note that 505 * this includes the PAUSED state, which can happen when the Activity is temporarily covered 506 * by another Activity's Fragment (e.g.). 507 * @return Whether any Activity under this Application is visible. 508 */ 509 @AnyThread 510 @CalledByNative hasVisibleActivities()511 public static boolean hasVisibleActivities() { 512 assert isInitialized(); 513 int state = getStateForApplication(); 514 return state == ApplicationState.HAS_RUNNING_ACTIVITIES 515 || state == ApplicationState.HAS_PAUSED_ACTIVITIES; 516 } 517 518 /** 519 * Checks to see if there are any active Activity instances being watched by ApplicationStatus. 520 * @return True if all Activities have been destroyed. 521 */ 522 @AnyThread isEveryActivityDestroyed()523 public static boolean isEveryActivityDestroyed() { 524 assert isInitialized(); 525 return sActivityInfo.isEmpty(); 526 } 527 528 /** 529 * Registers the given listener to receive state changes for all activities. 530 * @param listener Listener to receive state changes. 531 */ 532 @MainThread registerStateListenerForAllActivities(ActivityStateListener listener)533 public static void registerStateListenerForAllActivities(ActivityStateListener listener) { 534 assert isInitialized(); 535 sGeneralActivityStateListeners.addObserver(listener); 536 } 537 538 /** 539 * Registers the given listener to receive state changes for {@code activity}. After a call to 540 * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with 541 * {@link ActivityState#DESTROYED} all listeners associated with that particular 542 * {@link Activity} are removed. 543 * @param listener Listener to receive state changes. 544 * @param activity Activity to track or {@code null} to track all activities. 545 */ 546 @MainThread 547 @SuppressLint("NewApi") registerStateListenerForActivity( ActivityStateListener listener, Activity activity)548 public static void registerStateListenerForActivity( 549 ActivityStateListener listener, Activity activity) { 550 assert isInitialized(); 551 assert activity != null; 552 553 ActivityInfo info = sActivityInfo.get(activity); 554 assert info.getStatus() != ActivityState.DESTROYED; 555 info.getListeners().addObserver(listener); 556 } 557 558 /** 559 * Unregisters the given listener from receiving activity state changes. 560 * @param listener Listener that doesn't want to receive state changes. 561 */ 562 @MainThread unregisterActivityStateListener(ActivityStateListener listener)563 public static void unregisterActivityStateListener(ActivityStateListener listener) { 564 sGeneralActivityStateListeners.removeObserver(listener); 565 566 // Loop through all observer lists for all activities and remove the listener. 567 synchronized (sActivityInfo) { 568 for (ActivityInfo info : sActivityInfo.values()) { 569 info.getListeners().removeObserver(listener); 570 } 571 } 572 } 573 574 /** 575 * Registers the given listener to receive state changes for the application. 576 * @param listener Listener to receive state state changes. 577 */ 578 @MainThread registerApplicationStateListener(ApplicationStateListener listener)579 public static void registerApplicationStateListener(ApplicationStateListener listener) { 580 sApplicationStateListeners.addObserver(listener); 581 } 582 583 /** 584 * Unregisters the given listener from receiving state changes. 585 * @param listener Listener that doesn't want to receive state changes. 586 */ 587 @MainThread unregisterApplicationStateListener(ApplicationStateListener listener)588 public static void unregisterApplicationStateListener(ApplicationStateListener listener) { 589 sApplicationStateListeners.removeObserver(listener); 590 } 591 592 /** 593 * Robolectric JUnit tests create a new application between each test, while all the context 594 * in static classes isn't reset. This function allows to reset the application status to avoid 595 * being in a dirty state. 596 */ 597 @MainThread destroyForJUnitTests()598 public static void destroyForJUnitTests() { 599 synchronized (sActivityInfo) { 600 sApplicationStateListeners.clear(); 601 sGeneralActivityStateListeners.clear(); 602 sActivityInfo.clear(); 603 sWindowFocusListeners.clear(); 604 sCurrentApplicationState = ApplicationState.UNKNOWN; 605 sActivity = null; 606 sNativeApplicationStateListener = null; 607 } 608 } 609 610 /** 611 * Registers the single thread-safe native activity status listener. 612 * This handles the case where the caller is not on the main thread. 613 * Note that this is used by a leaky singleton object from the native 614 * side, hence lifecycle management is greatly simplified. 615 */ 616 @CalledByNative registerThreadSafeNativeApplicationStateListener()617 private static void registerThreadSafeNativeApplicationStateListener() { 618 ThreadUtils.runOnUiThread(new Runnable() { 619 @Override 620 public void run() { 621 if (sNativeApplicationStateListener != null) return; 622 623 sNativeApplicationStateListener = new ApplicationStateListener() { 624 @Override 625 public void onApplicationStateChange(int newState) { 626 ApplicationStatusJni.get().onApplicationStateChange(newState); 627 } 628 }; 629 registerApplicationStateListener(sNativeApplicationStateListener); 630 } 631 }); 632 } 633 634 /** 635 * Determines the current application state as defined by {@link ApplicationState}. This will 636 * loop over all the activities and check their state to determine what the general application 637 * state should be. 638 * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed. 639 * HAS_PAUSED_ACTIVITIES if none are running and one is paused. 640 * HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped. 641 * HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped. 642 */ 643 @ApplicationState 644 @GuardedBy("sActivityInfo") determineApplicationStateLocked()645 private static int determineApplicationStateLocked() { 646 boolean hasPausedActivity = false; 647 boolean hasStoppedActivity = false; 648 649 for (ActivityInfo info : sActivityInfo.values()) { 650 int state = info.getStatus(); 651 if (state != ActivityState.PAUSED && state != ActivityState.STOPPED 652 && state != ActivityState.DESTROYED) { 653 return ApplicationState.HAS_RUNNING_ACTIVITIES; 654 } else if (state == ActivityState.PAUSED) { 655 hasPausedActivity = true; 656 } else if (state == ActivityState.STOPPED) { 657 hasStoppedActivity = true; 658 } 659 } 660 661 if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES; 662 if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES; 663 return ApplicationState.HAS_DESTROYED_ACTIVITIES; 664 } 665 666 @NativeMethods 667 interface Natives { 668 // Called to notify the native side of state changes. 669 // IMPORTANT: This is always called on the main thread! onApplicationStateChange(@pplicationState int newState)670 void onApplicationStateChange(@ApplicationState int newState); 671 } 672 } 673