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