• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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