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