ChromeShellActivity.java revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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
5package org.chromium.chrome.shell;
6
7import android.app.Activity;
8import android.content.Intent;
9import android.content.res.TypedArray;
10import android.os.Bundle;
11import android.text.TextUtils;
12import android.util.Log;
13import android.view.KeyEvent;
14import android.view.Menu;
15import android.view.MenuItem;
16import android.widget.Toast;
17
18import com.google.common.annotations.VisibleForTesting;
19
20import org.chromium.base.ApiCompatibilityUtils;
21import org.chromium.base.BaseSwitches;
22import org.chromium.base.CommandLine;
23import org.chromium.base.MemoryPressureListener;
24import org.chromium.base.library_loader.ProcessInitException;
25import org.chromium.chrome.browser.DevToolsServer;
26import org.chromium.chrome.browser.appmenu.AppMenuHandler;
27import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
28import org.chromium.chrome.browser.printing.PrintingControllerFactory;
29import org.chromium.chrome.browser.printing.TabPrinter;
30import org.chromium.chrome.shell.sync.SyncController;
31import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
32import org.chromium.content.browser.ActivityContentVideoViewClient;
33import org.chromium.content.browser.BrowserStartupController;
34import org.chromium.content.browser.ContentView;
35import org.chromium.content.browser.DeviceUtils;
36import org.chromium.printing.PrintManagerDelegateImpl;
37import org.chromium.printing.PrintingController;
38import org.chromium.sync.signin.ChromeSigninController;
39import org.chromium.ui.base.ActivityWindowAndroid;
40import org.chromium.ui.base.WindowAndroid;
41
42/**
43 * The {@link android.app.Activity} component of a basic test shell to test Chrome features.
44 */
45public class ChromeShellActivity extends Activity implements AppMenuPropertiesDelegate {
46    private static final String TAG = "ChromeShellActivity";
47    private static final String CHROME_DISTILLER_SCHEME = "chrome-distiller";
48
49    /**
50     * Factory used to set up a mock ActivityWindowAndroid for testing.
51     */
52    public interface WindowAndroidFactoryForTest {
53        /**
54         * @return ActivityWindowAndroid for the given activity.
55         */
56        public ActivityWindowAndroid getActivityWindowAndroid(Activity activity);
57    }
58
59    private static WindowAndroidFactoryForTest sWindowAndroidFactory =
60            new WindowAndroidFactoryForTest() {
61                @Override
62                public ActivityWindowAndroid getActivityWindowAndroid(Activity activity) {
63                    return new ActivityWindowAndroid(activity);
64                }
65            };
66    private WindowAndroid mWindow;
67    private TabManager mTabManager;
68    private DevToolsServer mDevToolsServer;
69    private SyncController mSyncController;
70    private PrintingController mPrintingController;
71
72    private AppMenuHandler mAppMenuHandler;
73
74    @Override
75    protected void onCreate(final Bundle savedInstanceState) {
76        super.onCreate(savedInstanceState);
77
78        ChromeShellApplication.initCommandLine();
79        waitForDebuggerIfNeeded();
80
81        DeviceUtils.addDeviceSpecificUserAgentSwitch(this);
82
83        BrowserStartupController.StartupCallback callback =
84                new BrowserStartupController.StartupCallback() {
85                    @Override
86                    public void onSuccess(boolean alreadyStarted) {
87                        finishInitialization(savedInstanceState);
88                    }
89
90                    @Override
91                    public void onFailure() {
92                        Toast.makeText(ChromeShellActivity.this,
93                                       R.string.browser_process_initialization_failed,
94                                       Toast.LENGTH_SHORT).show();
95                        Log.e(TAG, "Chromium browser process initialization failed");
96                        finish();
97                    }
98                };
99        try {
100            BrowserStartupController.get(this).startBrowserProcessesAsync(callback);
101        } catch (ProcessInitException e) {
102            Log.e(TAG, "Unable to load native library.", e);
103            System.exit(-1);
104        }
105    }
106
107    private void finishInitialization(final Bundle savedInstanceState) {
108        setContentView(R.layout.chrome_shell_activity);
109        mTabManager = (TabManager) findViewById(R.id.tab_manager);
110
111        mWindow = sWindowAndroidFactory.getActivityWindowAndroid(this);
112        mWindow.restoreInstanceState(savedInstanceState);
113        mTabManager.initialize(mWindow, new ActivityContentVideoViewClient(this));
114
115        String startupUrl = getUrlFromIntent(getIntent());
116        if (!TextUtils.isEmpty(startupUrl)) {
117            mTabManager.setStartupUrl(startupUrl);
118        }
119        ChromeShellToolbar mToolbar = (ChromeShellToolbar) findViewById(R.id.toolbar);
120        mAppMenuHandler = new AppMenuHandler(this, this, R.menu.main_menu);
121        mToolbar.setMenuHandler(mAppMenuHandler);
122
123        mDevToolsServer = new DevToolsServer("chrome_shell");
124        mDevToolsServer.setRemoteDebuggingEnabled(true);
125
126        mPrintingController = PrintingControllerFactory.create(this);
127
128        mSyncController = SyncController.get(this);
129        // In case this method is called after the first onStart(), we need to inform the
130        // SyncController that we have started.
131        mSyncController.onStart();
132    }
133
134    @Override
135    protected void onDestroy() {
136        super.onDestroy();
137
138        if (mDevToolsServer != null) mDevToolsServer.destroy();
139        mDevToolsServer = null;
140    }
141
142    @Override
143    protected void onSaveInstanceState(Bundle outState) {
144        // TODO(dtrainor): Save/restore the tab state.
145        if (mWindow != null) mWindow.saveInstanceState(outState);
146    }
147
148    @Override
149    public boolean onKeyUp(int keyCode, KeyEvent event) {
150        if (keyCode == KeyEvent.KEYCODE_BACK) {
151            ChromeShellTab tab = getActiveTab();
152            if (tab != null && tab.getContentView().canGoBack()) {
153                tab.getContentView().goBack();
154                return true;
155            }
156        }
157
158        return super.onKeyUp(keyCode, event);
159    }
160
161    @Override
162    protected void onNewIntent(Intent intent) {
163        if (MemoryPressureListener.handleDebugIntent(this, intent.getAction())) return;
164
165        String url = getUrlFromIntent(intent);
166        if (!TextUtils.isEmpty(url)) {
167            ChromeShellTab tab = getActiveTab();
168            if (tab != null) tab.loadUrlWithSanitization(url);
169        }
170    }
171
172    @Override
173    protected void onStop() {
174        super.onStop();
175
176        ContentView view = getActiveContentView();
177        if (view != null) view.onHide();
178    }
179
180    @Override
181    protected void onStart() {
182        super.onStart();
183
184        ContentView view = getActiveContentView();
185        if (view != null) view.onShow();
186
187        if (mSyncController != null) {
188            mSyncController.onStart();
189        }
190    }
191
192    @Override
193    public void onActivityResult(int requestCode, int resultCode, Intent data) {
194        mWindow.onActivityResult(requestCode, resultCode, data);
195    }
196
197    /**
198     * @return The {@link WindowAndroid} associated with this activity.
199     */
200    public WindowAndroid getWindowAndroid() {
201        return mWindow;
202    }
203
204    /**
205     * @return The {@link ChromeShellTab} that is currently visible.
206     */
207    public ChromeShellTab getActiveTab() {
208        return mTabManager != null ? mTabManager.getCurrentTab() : null;
209    }
210
211    /**
212     * @return The ContentView of the active tab.
213     */
214    public ContentView getActiveContentView() {
215        ChromeShellTab tab = getActiveTab();
216        return tab != null ? tab.getContentView() : null;
217    }
218
219    /**
220     * Creates a {@link ChromeShellTab} with a URL specified by {@code url}.
221     *
222     * @param url The URL the new {@link ChromeShellTab} should start with.
223     */
224    @VisibleForTesting
225    public void createTab(String url) {
226        mTabManager.createTab(url);
227    }
228
229    /**
230     * Override the menu key event to show AppMenu.
231     */
232    @Override
233    public boolean onKeyDown(int keyCode, KeyEvent event) {
234        if (keyCode == KeyEvent.KEYCODE_MENU && event.getRepeatCount() == 0) {
235            mAppMenuHandler.showAppMenu(findViewById(R.id.menu_button), true, false);
236            return true;
237        }
238        return super.onKeyDown(keyCode, event);
239    }
240
241    @Override
242    public boolean onOptionsItemSelected(MenuItem item) {
243        switch (item.getItemId()) {
244            case R.id.signin:
245                if (ChromeSigninController.get(this).isSignedIn())
246                    SyncController.openSignOutDialog(getFragmentManager());
247                else
248                    SyncController.openSigninDialog(getFragmentManager());
249                return true;
250            case R.id.print:
251                if (getActiveTab() != null) {
252                    mPrintingController.startPrint(new TabPrinter(getActiveTab()),
253                            new PrintManagerDelegateImpl(this));
254                }
255                return true;
256            case R.id.distill_page:
257                ChromeShellTab activeTab = getActiveTab();
258                String viewUrl = DomDistillerUrlUtils.getDistillerViewUrlFromUrl(
259                        CHROME_DISTILLER_SCHEME, getActiveTab().getUrl());
260                activeTab.loadUrlWithSanitization(viewUrl);
261                return true;
262            case R.id.back_menu_id:
263                if (getActiveTab().canGoBack()) getActiveTab().goBack();
264                return true;
265            case R.id.forward_menu_id:
266                if (getActiveTab().canGoForward()) getActiveTab().goForward();
267                return true;
268            default:
269                return super.onOptionsItemSelected(item);
270        }
271    }
272
273    private void waitForDebuggerIfNeeded() {
274        if (CommandLine.getInstance().hasSwitch(BaseSwitches.WAIT_FOR_JAVA_DEBUGGER)) {
275            Log.e(TAG, "Waiting for Java debugger to connect...");
276            android.os.Debug.waitForDebugger();
277            Log.e(TAG, "Java debugger connected. Resuming execution.");
278        }
279    }
280
281    private static String getUrlFromIntent(Intent intent) {
282        return intent != null ? intent.getDataString() : null;
283    }
284
285    @Override
286    public boolean shouldShowAppMenu() {
287        return true;
288    }
289
290    @Override
291    public void prepareMenu(Menu menu) {
292        // Disable the "Back" menu item if there is no page to go to.
293        MenuItem backMenuItem = menu.findItem(R.id.back_menu_id);
294        backMenuItem.setEnabled(getActiveTab().canGoBack());
295
296        // Disable the "Forward" menu item if there is no page to go to.
297        MenuItem forwardMenuItem = menu.findItem(R.id.forward_menu_id);
298        forwardMenuItem.setEnabled(getActiveTab().canGoForward());
299
300        // ChromeShell does not know about bookmarks yet
301        menu.findItem(R.id.bookmark_this_page_id).setEnabled(false);
302
303        MenuItem signinItem = menu.findItem(R.id.signin);
304        if (ChromeSigninController.get(this).isSignedIn()) {
305            signinItem.setTitle(ChromeSigninController.get(this).getSignedInAccountName());
306        } else {
307            signinItem.setTitle(R.string.signin_sign_in);
308        }
309
310        menu.findItem(R.id.print).setVisible(ApiCompatibilityUtils.isPrintingSupported());
311
312        menu.findItem(R.id.distill_page).setVisible(
313                CommandLine.getInstance().hasSwitch(ChromeShellSwitches.ENABLE_DOM_DISTILLER));
314
315        menu.setGroupVisible(R.id.MAIN_MENU, true);
316    }
317
318    @Override
319    public boolean shouldShowIconRow() {
320        return true;
321    }
322
323    @Override
324    public int getMenuThemeResourceId() {
325        return android.R.style.Theme_Holo_Light;
326    }
327
328    @Override
329    public int getItemRowHeight() {
330        TypedArray a = obtainStyledAttributes(
331                new int[] {android.R.attr.listPreferredItemHeightSmall});
332        int itemRowHeight = a.getDimensionPixelSize(0, 0);
333        a.recycle();
334        return itemRowHeight;
335    }
336
337    @VisibleForTesting
338    public static void setActivityWindowAndroidFactory(WindowAndroidFactoryForTest factory) {
339        sWindowAndroidFactory = factory;
340    }
341}
342