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.
5 package org.chromium.chrome.shell;
7 import android.app.Activity;
8 import android.content.Intent;
9 import android.os.Bundle;
10 import android.text.TextUtils;
11 import android.util.Log;
12 import android.view.KeyEvent;
13 import android.view.Menu;
14 import android.view.MenuItem;
15 import android.view.View;
16 import android.view.ViewGroup;
17 import android.widget.Toast;
19 import org.chromium.base.ApiCompatibilityUtils;
20 import org.chromium.base.BaseSwitches;
21 import org.chromium.base.CommandLine;
22 import org.chromium.base.ContentUriUtils;
23 import org.chromium.base.MemoryPressureListener;
24 import org.chromium.base.VisibleForTesting;
25 import org.chromium.base.library_loader.ProcessInitException;
26 import org.chromium.chrome.browser.DevToolsServer;
27 import org.chromium.chrome.browser.FileProviderHelper;
28 import org.chromium.chrome.browser.appmenu.AppMenuHandler;
29 import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
30 import org.chromium.chrome.browser.dom_distiller.DomDistillerTabUtils;
31 import org.chromium.chrome.browser.nfc.BeamController;
32 import org.chromium.chrome.browser.nfc.BeamProvider;
33 import org.chromium.chrome.browser.printing.PrintingControllerFactory;
34 import org.chromium.chrome.browser.printing.TabPrinter;
35 import org.chromium.chrome.browser.share.ShareHelper;
36 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
37 import org.chromium.chrome.shell.sync.SyncController;
38 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
39 import org.chromium.content.app.ContentApplication;
40 import org.chromium.content.browser.ActivityContentVideoViewClient;
41 import org.chromium.content.browser.BrowserStartupController;
42 import org.chromium.content.browser.ContentViewCore;
43 import org.chromium.content.browser.DeviceUtils;
44 import org.chromium.printing.PrintManagerDelegateImpl;
45 import org.chromium.printing.PrintingController;
46 import org.chromium.sync.signin.AccountManagerHelper;
47 import org.chromium.sync.signin.ChromeSigninController;
48 import org.chromium.ui.base.ActivityWindowAndroid;
49 import org.chromium.ui.base.WindowAndroid;
52 * The {@link android.app.Activity} component of a basic test shell to test Chrome features.
54 public class ChromeShellActivity extends Activity implements AppMenuPropertiesDelegate {
55 private static final String TAG = "ChromeShellActivity";
58 * Factory used to set up a mock ActivityWindowAndroid for testing.
60 public interface ActivityWindowAndroidFactory {
62 * @return ActivityWindowAndroid for the given activity.
64 public ActivityWindowAndroid getActivityWindowAndroid(Activity activity);
67 private static ActivityWindowAndroidFactory sWindowAndroidFactory =
68 new ActivityWindowAndroidFactory() {
70 public ActivityWindowAndroid getActivityWindowAndroid(Activity activity) {
71 return new ActivityWindowAndroid(activity);
75 private WindowAndroid mWindow;
76 private TabManager mTabManager;
77 private ChromeShellToolbar mToolbar;
78 private DevToolsServer mDevToolsServer;
79 private SyncController mSyncController;
80 private PrintingController mPrintingController;
83 * Factory used to set up a mock AppMenuHandler for testing.
85 public interface AppMenuHandlerFactory {
87 * @return AppMenuHandler for the given activity and menu resource id.
89 public AppMenuHandler getAppMenuHandler(Activity activity,
90 AppMenuPropertiesDelegate delegate, int menuResourceId);
93 private static AppMenuHandlerFactory sAppMenuHandlerFactory =
94 new AppMenuHandlerFactory() {
96 public AppMenuHandler getAppMenuHandler(Activity activity,
97 AppMenuPropertiesDelegate delegate, int menuResourceId) {
98 return new AppMenuHandler(activity, delegate, menuResourceId);
101 private AppMenuHandler mAppMenuHandler;
104 protected void onCreate(final Bundle savedInstanceState) {
105 super.onCreate(savedInstanceState);
107 ContentApplication.initCommandLine(this);
108 waitForDebuggerIfNeeded();
110 DeviceUtils.addDeviceSpecificUserAgentSwitch(this);
112 BrowserStartupController.StartupCallback callback =
113 new BrowserStartupController.StartupCallback() {
115 public void onSuccess(boolean alreadyStarted) {
116 finishInitialization(savedInstanceState);
120 public void onFailure() {
121 Toast.makeText(ChromeShellActivity.this,
122 R.string.browser_process_initialization_failed,
123 Toast.LENGTH_SHORT).show();
124 Log.e(TAG, "Chromium browser process initialization failed");
129 BrowserStartupController.get(this).startBrowserProcessesAsync(callback);
130 } catch (ProcessInitException e) {
131 Log.e(TAG, "Unable to load native library.", e);
136 private void finishInitialization(final Bundle savedInstanceState) {
137 setContentView(R.layout.chrome_shell_activity);
138 mTabManager = (TabManager) findViewById(R.id.tab_manager);
140 mWindow = sWindowAndroidFactory.getActivityWindowAndroid(this);
141 mWindow.restoreInstanceState(savedInstanceState);
142 mTabManager.initialize(mWindow, new ActivityContentVideoViewClient(this) {
144 public void enterFullscreenVideo(View view) {
145 super.enterFullscreenVideo(view);
146 if (mTabManager != null) {
147 mTabManager.setOverlayVideoMode(true);
152 public void exitFullscreenVideo() {
153 super.exitFullscreenVideo();
154 if (mTabManager != null) {
155 mTabManager.setOverlayVideoMode(false);
160 String startupUrl = getUrlFromIntent(getIntent());
161 if (!TextUtils.isEmpty(startupUrl)) {
162 mTabManager.setStartupUrl(startupUrl);
164 mToolbar = (ChromeShellToolbar) findViewById(R.id.toolbar);
165 mAppMenuHandler = sAppMenuHandlerFactory.getAppMenuHandler(this, this, R.menu.main_menu);
166 mToolbar.setMenuHandler(mAppMenuHandler);
168 mDevToolsServer = new DevToolsServer("chrome_shell");
169 mDevToolsServer.setRemoteDebuggingEnabled(
170 true, DevToolsServer.Security.ALLOW_DEBUG_PERMISSION);
172 mPrintingController = PrintingControllerFactory.create(this);
174 mSyncController = SyncController.get(this);
175 // In case this method is called after the first onStart(), we need to inform the
176 // SyncController that we have started.
177 mSyncController.onStart();
178 ContentUriUtils.setFileProviderUtil(new FileProviderHelper());
180 BeamController.registerForBeam(this, new BeamProvider() {
182 public String getTabUrlForBeam() {
183 ChromeShellTab tab = getActiveTab();
184 if (tab == null) return null;
191 protected void onDestroy() {
194 if (mDevToolsServer != null) mDevToolsServer.destroy();
195 mDevToolsServer = null;
199 protected void onSaveInstanceState(Bundle outState) {
200 // TODO(dtrainor): Save/restore the tab state.
201 if (mWindow != null) mWindow.saveInstanceState(outState);
205 public boolean onKeyUp(int keyCode, KeyEvent event) {
206 if (keyCode == KeyEvent.KEYCODE_BACK) {
207 if (mTabManager.isTabSwitcherVisible()) {
208 mTabManager.hideTabSwitcher();
211 ChromeShellTab tab = getActiveTab();
212 if (tab != null && tab.canGoBack()) {
217 return super.onKeyUp(keyCode, event);
221 protected void onNewIntent(Intent intent) {
222 if (MemoryPressureListener.handleDebugIntent(this, intent.getAction())) return;
224 String url = getUrlFromIntent(intent);
225 if (!TextUtils.isEmpty(url)) {
226 ChromeShellTab tab = getActiveTab();
227 if (tab != null) tab.loadUrlWithSanitization(url);
232 protected void onStop() {
235 if (mToolbar != null) mToolbar.hideSuggestions();
237 ContentViewCore viewCore = getActiveContentViewCore();
238 if (viewCore != null) viewCore.onHide();
242 protected void onStart() {
245 ContentViewCore viewCore = getActiveContentViewCore();
246 if (viewCore != null) viewCore.onShow();
248 if (mSyncController != null) {
249 mSyncController.onStart();
254 public void onActivityResult(int requestCode, int resultCode, Intent data) {
255 mWindow.onActivityResult(requestCode, resultCode, data);
259 * @return The {@link WindowAndroid} associated with this activity.
261 public WindowAndroid getWindowAndroid() {
266 * @return The {@link ChromeShellTab} that is currently visible.
268 public ChromeShellTab getActiveTab() {
269 return mTabManager != null ? mTabManager.getCurrentTab() : null;
273 * @return The ContentViewCore of the active tab.
275 public ContentViewCore getActiveContentViewCore() {
276 ChromeShellTab tab = getActiveTab();
277 return tab != null ? tab.getContentViewCore() : null;
281 * Creates a {@link ChromeShellTab} with a URL specified by {@code url}.
283 * @param url The URL the new {@link ChromeShellTab} should start with.
286 public void createTab(String url) {
287 mTabManager.createTab(url, TabLaunchType.FROM_EXTERNAL_APP);
291 * Closes all current tabs.
293 public void closeAllTabs() {
294 mTabManager.closeAllTabs();
298 * Override the menu key event to show AppMenu.
301 public boolean onKeyDown(int keyCode, KeyEvent event) {
302 if (keyCode == KeyEvent.KEYCODE_MENU && event.getRepeatCount() == 0) {
303 if (mToolbar != null) mToolbar.hideSuggestions();
304 mAppMenuHandler.showAppMenu(findViewById(R.id.menu_button), true, false);
307 return super.onKeyDown(keyCode, event);
310 @SuppressWarnings("deprecation")
312 public boolean onOptionsItemSelected(MenuItem item) {
313 ChromeShellTab activeTab = getActiveTab();
314 if (activeTab != null) {
315 ViewGroup containerView = activeTab.getContentViewCore().getContainerView();
316 if (containerView.isFocusable() && containerView.isFocusableInTouchMode()) {
317 containerView.requestFocus();
320 int id = item.getItemId();
321 if (id == R.id.signin) {
322 if (ChromeSigninController.get(this).isSignedIn()) {
323 SyncController.openSignOutDialog(getFragmentManager());
324 } else if (AccountManagerHelper.get(this).hasGoogleAccounts()) {
325 SyncController.openSigninDialog(getFragmentManager());
327 Toast.makeText(this, R.string.signin_no_account, Toast.LENGTH_SHORT).show();
330 } else if (id == R.id.print) {
331 if (activeTab != null) {
332 mPrintingController.startPrint(new TabPrinter(activeTab),
333 new PrintManagerDelegateImpl(this));
336 } else if (id == R.id.distill_page) {
337 if (activeTab != null) {
338 DomDistillerTabUtils.distillCurrentPageAndView(
339 activeTab.getContentViewCore().getWebContents());
342 } else if (id == R.id.back_menu_id) {
343 if (activeTab != null && activeTab.canGoBack()) {
347 } else if (id == R.id.forward_menu_id) {
348 if (activeTab != null && activeTab.canGoForward()) {
349 activeTab.goForward();
352 } else if (id == R.id.new_tab_menu_id) {
353 mTabManager.createNewTab();
355 } else if (id == R.id.share_menu_id || id == R.id.direct_share_menu_id) {
356 ShareHelper.share(item.getItemId() == R.id.direct_share_menu_id, this,
357 activeTab.getTitle(), activeTab.getUrl(), null,
358 Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
361 return super.onOptionsItemSelected(item);
365 private void waitForDebuggerIfNeeded() {
366 if (CommandLine.getInstance().hasSwitch(BaseSwitches.WAIT_FOR_JAVA_DEBUGGER)) {
367 Log.e(TAG, "Waiting for Java debugger to connect...");
368 android.os.Debug.waitForDebugger();
369 Log.e(TAG, "Java debugger connected. Resuming execution.");
373 private static String getUrlFromIntent(Intent intent) {
374 return intent != null ? intent.getDataString() : null;
378 public boolean shouldShowAppMenu() {
383 public void prepareMenu(Menu menu) {
384 menu.setGroupVisible(R.id.MAIN_MENU, true);
385 ChromeShellTab activeTab = getActiveTab();
387 // Disable the "Back" menu item if there is no page to go to.
388 MenuItem backMenuItem = menu.findItem(R.id.back_menu_id);
389 backMenuItem.setEnabled(activeTab != null ? activeTab.canGoBack() : false);
391 // Disable the "Forward" menu item if there is no page to go to.
392 MenuItem forwardMenuItem = menu.findItem(R.id.forward_menu_id);
393 forwardMenuItem.setEnabled(activeTab != null ? activeTab.canGoForward() : false);
395 // ChromeShell does not know about bookmarks yet
396 menu.findItem(R.id.bookmark_this_page_id).setEnabled(true);
398 MenuItem signinItem = menu.findItem(R.id.signin);
399 if (ChromeSigninController.get(this).isSignedIn()) {
400 signinItem.setTitle(ChromeSigninController.get(this).getSignedInAccountName());
402 signinItem.setTitle(R.string.signin_sign_in);
405 menu.findItem(R.id.print).setVisible(ApiCompatibilityUtils.isPrintingSupported());
407 MenuItem distillPageItem = menu.findItem(R.id.distill_page);
408 if (CommandLine.getInstance().hasSwitch(ChromeShellSwitches.ENABLE_DOM_DISTILLER)) {
409 String url = activeTab != null ? activeTab.getUrl() : null;
410 distillPageItem.setEnabled(!DomDistillerUrlUtils.isDistilledPage(url));
411 distillPageItem.setVisible(true);
413 distillPageItem.setVisible(false);
415 ShareHelper.configureDirectShareMenuItem(this, menu.findItem(R.id.direct_share_menu_id));
419 public AppMenuHandler getAppMenuHandler() {
420 return mAppMenuHandler;
424 public int getMenuThemeResourceId() {
425 return R.style.OverflowMenuTheme;
429 public int getMenuButtonStartPaddingDimenId() {
434 public static void setActivityWindowAndroidFactory(ActivityWindowAndroidFactory factory) {
435 sWindowAndroidFactory = factory;
439 public static void setAppMenuHandlerFactory(AppMenuHandlerFactory factory) {
440 sAppMenuHandlerFactory = factory;