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