1// Copyright 2012 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.testshell;
6
7import android.app.Activity;
8import android.content.Intent;
9import android.content.res.TypedArray;
10import android.os.Bundle;
11import android.text.TextUtils;
12import android.util.Log;
13import android.view.KeyEvent;
14import android.view.Menu;
15import android.view.MenuItem;
16import android.widget.Toast;
17
18import org.chromium.base.ApiCompatibilityUtils;
19import org.chromium.base.BaseSwitches;
20import org.chromium.base.CommandLine;
21import org.chromium.base.MemoryPressureListener;
22import org.chromium.chrome.browser.DevToolsServer;
23import org.chromium.chrome.browser.appmenu.AppMenuHandler;
24import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
25import org.chromium.chrome.browser.printing.PrintingControllerFactory;
26import org.chromium.chrome.browser.printing.TabPrinter;
27import org.chromium.chrome.testshell.sync.SyncController;
28import org.chromium.content.browser.ActivityContentVideoViewClient;
29import org.chromium.content.browser.BrowserStartupController;
30import org.chromium.content.browser.ContentVideoViewClient;
31import org.chromium.content.browser.ContentView;
32import org.chromium.content.browser.ContentViewClient;
33import org.chromium.content.browser.DeviceUtils;
34import org.chromium.content.common.ProcessInitException;
35import org.chromium.printing.PrintingController;
36import org.chromium.sync.signin.ChromeSigninController;
37import org.chromium.ui.base.ActivityWindowAndroid;
38import org.chromium.ui.base.WindowAndroid;
39
40/**
41 * The {@link android.app.Activity} component of a basic test shell to test Chrome features.
42 */
43public class ChromiumTestShellActivity extends Activity implements AppMenuPropertiesDelegate {
44    private static final String TAG = "ChromiumTestShellActivity";
45
46    private WindowAndroid mWindow;
47    private TabManager mTabManager;
48    private DevToolsServer mDevToolsServer;
49    private SyncController mSyncController;
50    private PrintingController mPrintingController;
51
52    private AppMenuHandler mAppMenuHandler;
53
54    @Override
55    protected void onCreate(final Bundle savedInstanceState) {
56        super.onCreate(savedInstanceState);
57
58        ChromiumTestShellApplication.initCommandLine();
59        waitForDebuggerIfNeeded();
60
61        DeviceUtils.addDeviceSpecificUserAgentSwitch(this);
62
63        BrowserStartupController.StartupCallback callback =
64                new BrowserStartupController.StartupCallback() {
65                    @Override
66                    public void onSuccess(boolean alreadyStarted) {
67                        finishInitialization(savedInstanceState);
68                    }
69
70                    @Override
71                    public void onFailure() {
72                        Toast.makeText(ChromiumTestShellActivity.this,
73                                       R.string.browser_process_initialization_failed,
74                                       Toast.LENGTH_SHORT).show();
75                        Log.e(TAG, "Chromium browser process initialization failed");
76                        finish();
77                    }
78                };
79        try {
80            BrowserStartupController.get(this).startBrowserProcessesAsync(callback);
81        }
82        catch (ProcessInitException e) {
83            Log.e(TAG, "Unable to load native library.", e);
84            System.exit(-1);
85        }
86    }
87
88    private void finishInitialization(final Bundle savedInstanceState) {
89        setContentView(R.layout.testshell_activity);
90        mTabManager = (TabManager) findViewById(R.id.tab_manager);
91
92        mWindow = new ActivityWindowAndroid(this);
93        mWindow.restoreInstanceState(savedInstanceState);
94        mTabManager.setWindow(mWindow);
95
96        String startupUrl = getUrlFromIntent(getIntent());
97        if (!TextUtils.isEmpty(startupUrl)) {
98            mTabManager.setStartupUrl(startupUrl);
99        }
100        TestShellToolbar mToolbar = (TestShellToolbar) findViewById(R.id.toolbar);
101        mAppMenuHandler = new AppMenuHandler(this, this, R.menu.main_menu);
102        mToolbar.setMenuHandler(mAppMenuHandler);
103
104        mDevToolsServer = new DevToolsServer("chromium_testshell");
105        mDevToolsServer.setRemoteDebuggingEnabled(true);
106
107        mPrintingController = PrintingControllerFactory.create(this);
108
109        mSyncController = SyncController.get(this);
110        // In case this method is called after the first onStart(), we need to inform the
111        // SyncController that we have started.
112        mSyncController.onStart();
113    }
114
115    @Override
116    protected void onDestroy() {
117        super.onDestroy();
118
119        if (mDevToolsServer != null)
120            mDevToolsServer.destroy();
121        mDevToolsServer = null;
122    }
123
124    @Override
125    protected void onSaveInstanceState(Bundle outState) {
126        // TODO(dtrainor): Save/restore the tab state.
127        if (mWindow != null) mWindow.saveInstanceState(outState);
128    }
129
130    @Override
131    public boolean onKeyUp(int keyCode, KeyEvent event) {
132        if (keyCode == KeyEvent.KEYCODE_BACK) {
133            TestShellTab tab = getActiveTab();
134            if (tab != null && tab.getContentView().canGoBack()) {
135                tab.getContentView().goBack();
136                return true;
137            }
138        }
139
140        return super.onKeyUp(keyCode, event);
141    }
142
143    @Override
144    protected void onNewIntent(Intent intent) {
145        if (MemoryPressureListener.handleDebugIntent(this, intent.getAction())) return;
146
147        String url = getUrlFromIntent(intent);
148        if (!TextUtils.isEmpty(url)) {
149            TestShellTab tab = getActiveTab();
150            if (tab != null) tab.loadUrlWithSanitization(url);
151        }
152    }
153
154    @Override
155    protected void onStop() {
156        super.onStop();
157
158        ContentView view = getActiveContentView();
159        if (view != null) view.onHide();
160    }
161
162    @Override
163    protected void onStart() {
164        super.onStart();
165
166        ContentView view = getActiveContentView();
167        if (view != null) view.onShow();
168
169        if (mSyncController != null) {
170            mSyncController.onStart();
171        }
172    }
173
174    @Override
175    public void onActivityResult(int requestCode, int resultCode, Intent data) {
176        mWindow.onActivityResult(requestCode, resultCode, data);
177    }
178
179    /**
180     * @return The {@link WindowAndroid} associated with this activity.
181     */
182    public WindowAndroid getWindowAndroid() {
183        return mWindow;
184    }
185
186    /**
187     * @return The {@link TestShellTab} that is currently visible.
188     */
189    public TestShellTab getActiveTab() {
190        return mTabManager != null ? mTabManager.getCurrentTab() : null;
191    }
192
193    /**
194     * @return The ContentView of the active tab.
195     */
196    public ContentView getActiveContentView() {
197        TestShellTab tab = getActiveTab();
198        return tab != null ? tab.getContentView() : null;
199    }
200
201    /**
202     * Creates a {@link TestShellTab} with a URL specified by {@code url}.
203     *
204     * @param url The URL the new {@link TestShellTab} should start with.
205     */
206    public void createTab(String url) {
207        mTabManager.createTab(url);
208        getActiveContentView().setContentViewClient(new ContentViewClient() {
209            @Override
210            public ContentVideoViewClient getContentVideoViewClient() {
211                return new ActivityContentVideoViewClient(ChromiumTestShellActivity.this);
212            }
213        });
214    }
215
216    /**
217     * Override the menu key event to show AppMenu.
218     */
219    @Override
220    public boolean onKeyDown(int keyCode, KeyEvent event) {
221        if (keyCode == KeyEvent.KEYCODE_MENU && event.getRepeatCount() == 0) {
222            mAppMenuHandler.showAppMenu(findViewById(R.id.menu_button), true, false);
223            return true;
224        }
225        return super.onKeyDown(keyCode, event);
226    }
227
228    @Override
229    public boolean onOptionsItemSelected(MenuItem item) {
230        switch (item.getItemId()) {
231            case R.id.signin:
232                if (ChromeSigninController.get(this).isSignedIn())
233                    SyncController.openSignOutDialog(getFragmentManager());
234                else
235                    SyncController.openSigninDialog(getFragmentManager());
236                return true;
237            case R.id.print:
238                if (getActiveTab() != null) {
239                    mPrintingController.startPrint(new TabPrinter(getActiveTab()));
240                }
241                return true;
242            case R.id.back_menu_id:
243                if (getActiveTab().canGoBack()) getActiveTab().goBack();
244                return true;
245            case R.id.forward_menu_id:
246                if (getActiveTab().canGoForward()) getActiveTab().goForward();
247                return true;
248            default:
249                return super.onOptionsItemSelected(item);
250        }
251    }
252
253    private void waitForDebuggerIfNeeded() {
254        if (CommandLine.getInstance().hasSwitch(BaseSwitches.WAIT_FOR_JAVA_DEBUGGER)) {
255            Log.e(TAG, "Waiting for Java debugger to connect...");
256            android.os.Debug.waitForDebugger();
257            Log.e(TAG, "Java debugger connected. Resuming execution.");
258        }
259    }
260
261    private static String getUrlFromIntent(Intent intent) {
262        return intent != null ? intent.getDataString() : null;
263    }
264
265    @Override
266    public boolean shouldShowAppMenu() {
267        return true;
268    }
269
270    @Override
271    public void prepareMenu(Menu menu) {
272        // Disable the "Back" menu item if there is no page to go to.
273        MenuItem backMenuItem = menu.findItem(R.id.back_menu_id);
274        backMenuItem.setEnabled(getActiveTab().canGoBack());
275
276        // Disable the "Forward" menu item if there is no page to go to.
277        MenuItem forwardMenuItem = menu.findItem(R.id.forward_menu_id);
278        forwardMenuItem.setEnabled(getActiveTab().canGoForward());
279
280        // ChromiumTestShell does not know about bookmarks yet
281        menu.findItem(R.id.bookmark_this_page_id).setEnabled(false);
282
283        MenuItem signinItem = menu.findItem(R.id.signin);
284        if (ChromeSigninController.get(this).isSignedIn()) {
285            signinItem.setTitle(ChromeSigninController.get(this).getSignedInAccountName());
286        } else {
287            signinItem.setTitle(R.string.signin_sign_in);
288        }
289
290        menu.findItem(R.id.print).setVisible(ApiCompatibilityUtils.isPrintingSupported());
291
292        menu.setGroupVisible(R.id.MAIN_MENU, true);
293    }
294
295    @Override
296    public boolean shouldShowIconRow() {
297        return true;
298    }
299
300    @Override
301    public int getMenuThemeResourceId() {
302        return android.R.style.Theme_Holo_Light;
303    }
304
305    @Override
306    public int getItemRowHeight() {
307        TypedArray a = obtainStyledAttributes(
308                new int[] {android.R.attr.listPreferredItemHeightSmall});
309        int itemRowHeight = a.getDimensionPixelSize(0, 0);
310        a.recycle();
311        return itemRowHeight;
312    }
313}
314