1 // Copyright 2013 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.app.Activity; 8 import android.app.Application; 9 import android.content.Context; 10 import android.os.Bundle; 11 import android.view.Window; 12 13 import org.chromium.base.multidex.ChromiumMultiDexInstaller; 14 15 import java.lang.reflect.InvocationHandler; 16 import java.lang.reflect.InvocationTargetException; 17 import java.lang.reflect.Method; 18 import java.lang.reflect.Proxy; 19 20 /** 21 * Basic application functionality that should be shared among all browser applications. 22 */ 23 public class BaseChromiumApplication extends Application { 24 25 private static final String TAG = "cr.base"; 26 private static final String TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS = 27 "android.support.v7.internal.app.ToolbarActionBar$ToolbarCallbackWrapper"; 28 // In builds using the --use_unpublished_apis flag, the ToolbarActionBar class name does not 29 // include the "internal" package. 30 private static final String TOOLBAR_CALLBACK_WRAPPER_CLASS = 31 "android.support.v7.app.ToolbarActionBar$ToolbarCallbackWrapper"; 32 private final boolean mShouldInitializeApplicationStatusTracking; 33 BaseChromiumApplication()34 public BaseChromiumApplication() { 35 this(true); 36 } 37 BaseChromiumApplication(boolean shouldInitializeApplicationStatusTracking)38 protected BaseChromiumApplication(boolean shouldInitializeApplicationStatusTracking) { 39 mShouldInitializeApplicationStatusTracking = shouldInitializeApplicationStatusTracking; 40 } 41 42 @Override attachBaseContext(Context base)43 protected void attachBaseContext(Context base) { 44 super.attachBaseContext(base); 45 ChromiumMultiDexInstaller.install(this); 46 } 47 48 /** 49 * Interface to be implemented by listeners for window focus events. 50 */ 51 public interface WindowFocusChangedListener { 52 /** 53 * Called when the window focus changes for {@code activity}. 54 * @param activity The {@link Activity} that has a window focus changed event. 55 * @param hasFocus Whether or not {@code activity} gained or lost focus. 56 */ onWindowFocusChanged(Activity activity, boolean hasFocus)57 public void onWindowFocusChanged(Activity activity, boolean hasFocus); 58 } 59 60 private ObserverList<WindowFocusChangedListener> mWindowFocusListeners = 61 new ObserverList<WindowFocusChangedListener>(); 62 63 /** 64 * Intercepts calls to an existing Window.Callback. Most invocations are passed on directly 65 * to the composed Window.Callback but enables intercepting/manipulating others. 66 * 67 * This is used to relay window focus changes throughout the app and remedy a bug in the 68 * appcompat library. 69 */ 70 private class WindowCallbackProxy implements InvocationHandler { 71 private final Window.Callback mCallback; 72 private final Activity mActivity; 73 WindowCallbackProxy(Activity activity, Window.Callback callback)74 public WindowCallbackProxy(Activity activity, Window.Callback callback) { 75 mCallback = callback; 76 mActivity = activity; 77 } 78 79 @Override invoke(Object proxy, Method method, Object[] args)80 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 81 if (method.getName().equals("onWindowFocusChanged") && args.length == 1 82 && args[0] instanceof Boolean) { 83 onWindowFocusChanged((boolean) args[0]); 84 return null; 85 } else { 86 try { 87 return method.invoke(mCallback, args); 88 } catch (InvocationTargetException e) { 89 // Special-case for when a method is not defined on the underlying 90 // Window.Callback object. Because we're using a Proxy to forward all method 91 // calls, this breaks the Android framework's handling for apps built against 92 // an older SDK. The framework expects an AbstractMethodError but due to 93 // reflection it becomes wrapped inside an InvocationTargetException. Undo the 94 // wrapping to signal the framework accordingly. 95 if (e.getCause() instanceof AbstractMethodError) { 96 throw e.getCause(); 97 } 98 throw e; 99 } 100 } 101 } 102 onWindowFocusChanged(boolean hasFocus)103 public void onWindowFocusChanged(boolean hasFocus) { 104 mCallback.onWindowFocusChanged(hasFocus); 105 106 for (WindowFocusChangedListener listener : mWindowFocusListeners) { 107 listener.onWindowFocusChanged(mActivity, hasFocus); 108 } 109 } 110 } 111 112 @Override onCreate()113 public void onCreate() { 114 super.onCreate(); 115 116 if (mShouldInitializeApplicationStatusTracking) startTrackingApplicationStatus(); 117 } 118 119 /** 120 * Registers a listener to receive window focus updates on activities in this application. 121 * @param listener Listener to receive window focus events. 122 */ registerWindowFocusChangedListener(WindowFocusChangedListener listener)123 public void registerWindowFocusChangedListener(WindowFocusChangedListener listener) { 124 mWindowFocusListeners.addObserver(listener); 125 } 126 127 /** 128 * Unregisters a listener from receiving window focus updates on activities in this application. 129 * @param listener Listener that doesn't want to receive window focus events. 130 */ unregisterWindowFocusChangedListener(WindowFocusChangedListener listener)131 public void unregisterWindowFocusChangedListener(WindowFocusChangedListener listener) { 132 mWindowFocusListeners.removeObserver(listener); 133 } 134 135 /** Initializes the {@link CommandLine}. */ initCommandLine()136 public void initCommandLine() {} 137 138 /** 139 * This must only be called for contexts whose application is a subclass of 140 * {@link BaseChromiumApplication}. 141 */ 142 @VisibleForTesting initCommandLine(Context context)143 public static void initCommandLine(Context context) { 144 ((BaseChromiumApplication) context.getApplicationContext()).initCommandLine(); 145 } 146 147 /** Register hooks and listeners to start tracking the application status. */ startTrackingApplicationStatus()148 private void startTrackingApplicationStatus() { 149 ApplicationStatus.initialize(this); 150 registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { 151 @Override 152 public void onActivityCreated(final Activity activity, Bundle savedInstanceState) { 153 Window.Callback callback = activity.getWindow().getCallback(); 154 activity.getWindow().setCallback((Window.Callback) Proxy.newProxyInstance( 155 Window.Callback.class.getClassLoader(), new Class[] {Window.Callback.class}, 156 new WindowCallbackProxy(activity, callback))); 157 } 158 159 @Override 160 public void onActivityDestroyed(Activity activity) { 161 if (BuildConfig.IS_DEBUG) { 162 assert (Proxy.isProxyClass(activity.getWindow().getCallback().getClass()) 163 || activity.getWindow().getCallback().getClass().getName().equals( 164 TOOLBAR_CALLBACK_WRAPPER_CLASS) 165 || activity.getWindow().getCallback().getClass().getName().equals( 166 TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS)); 167 } 168 } 169 170 @Override 171 public void onActivityPaused(Activity activity) { 172 if (BuildConfig.IS_DEBUG) { 173 assert (Proxy.isProxyClass(activity.getWindow().getCallback().getClass()) 174 || activity.getWindow().getCallback().getClass().getName().equals( 175 TOOLBAR_CALLBACK_WRAPPER_CLASS) 176 || activity.getWindow().getCallback().getClass().getName().equals( 177 TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS)); 178 } 179 } 180 181 @Override 182 public void onActivityResumed(Activity activity) { 183 if (BuildConfig.IS_DEBUG) { 184 assert (Proxy.isProxyClass(activity.getWindow().getCallback().getClass()) 185 || activity.getWindow().getCallback().getClass().getName().equals( 186 TOOLBAR_CALLBACK_WRAPPER_CLASS) 187 || activity.getWindow().getCallback().getClass().getName().equals( 188 TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS)); 189 } 190 } 191 192 @Override 193 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 194 if (BuildConfig.IS_DEBUG) { 195 assert (Proxy.isProxyClass(activity.getWindow().getCallback().getClass()) 196 || activity.getWindow().getCallback().getClass().getName().equals( 197 TOOLBAR_CALLBACK_WRAPPER_CLASS) 198 || activity.getWindow().getCallback().getClass().getName().equals( 199 TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS)); 200 } 201 } 202 203 @Override 204 public void onActivityStarted(Activity activity) { 205 if (BuildConfig.IS_DEBUG) { 206 assert (Proxy.isProxyClass(activity.getWindow().getCallback().getClass()) 207 || activity.getWindow().getCallback().getClass().getName().equals( 208 TOOLBAR_CALLBACK_WRAPPER_CLASS) 209 || activity.getWindow().getCallback().getClass().getName().equals( 210 TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS)); 211 } 212 } 213 214 @Override 215 public void onActivityStopped(Activity activity) { 216 if (BuildConfig.IS_DEBUG) { 217 assert (Proxy.isProxyClass(activity.getWindow().getCallback().getClass()) 218 || activity.getWindow().getCallback().getClass().getName().equals( 219 TOOLBAR_CALLBACK_WRAPPER_CLASS) 220 || activity.getWindow().getCallback().getClass().getName().equals( 221 TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS)); 222 } 223 } 224 }); 225 } 226 } 227