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