Index: chrome/android/java_staging/src/org/chromium/chrome/browser/webapps/WebappActivity.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/webapps/WebappActivity.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a953abf769507c0a57728262e48e8d8b2f2d43b3 |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/webapps/WebappActivity.java |
@@ -0,0 +1,359 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.webapps; |
+ |
+import android.content.Context; |
+import android.content.Intent; |
+import android.graphics.Bitmap; |
+import android.net.Uri; |
+import android.os.AsyncTask; |
+import android.os.Bundle; |
+import android.text.TextUtils; |
+import android.util.Log; |
+import android.view.View; |
+ |
+import com.google.android.apps.chrome.R; |
+ |
+import org.chromium.base.ActivityState; |
+import org.chromium.base.ApiCompatibilityUtils; |
+import org.chromium.base.ApplicationStatus; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.chrome.browser.EmptyTabObserver; |
+import org.chromium.chrome.browser.ShortcutHelper; |
+import org.chromium.chrome.browser.Tab; |
+import org.chromium.chrome.browser.TabObserver; |
+import org.chromium.chrome.browser.UrlUtilities; |
+import org.chromium.chrome.browser.document.DocumentUtils; |
+import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
+import org.chromium.chrome.browser.ssl.ConnectionSecurityHelperSecurityLevel; |
+import org.chromium.chrome.browser.util.FeatureUtilities; |
+import org.chromium.content.browser.ScreenOrientationProvider; |
+import org.chromium.content_public.browser.LoadUrlParams; |
+import org.chromium.content_public.browser.WebContentsObserver; |
+import org.chromium.net.NetworkChangeNotifier; |
+import org.chromium.ui.base.PageTransition; |
+ |
+import java.io.File; |
+ |
+/** |
+ * Displays a webapp in a nearly UI-less Chrome (InfoBars still appear). |
+ */ |
+public class WebappActivity extends FullScreenActivity { |
+ public static final String WEBAPP_SCHEME = "webapp"; |
+ |
+ private static final String TAG = "WebappActivity"; |
+ private static final long MS_BEFORE_NAVIGATING_BACK_FROM_INTERSTITIAL = 1000; |
+ |
+ private final WebappInfo mWebappInfo; |
+ private AsyncTask<Void, Void, Void> mCleanupTask; |
+ |
+ private WebContentsObserver mWebContentsObserver; |
+ |
+ private WebappUrlBar mUrlBar; |
+ |
+ private boolean mIsInitialized; |
+ private Integer mBrandColor; |
+ |
+ /** |
+ * Construct all the variables that shouldn't change. We do it here both to clarify when the |
+ * objects are created and to ensure that they exist throughout the parallelized initialization |
+ * of the WebappActivity. |
+ */ |
+ public WebappActivity() { |
+ mWebappInfo = WebappInfo.createEmpty(); |
+ } |
+ |
+ @Override |
+ protected void onNewIntent(Intent intent) { |
+ if (intent == null) return; |
+ super.onNewIntent(intent); |
+ |
+ WebappInfo newWebappInfo = WebappInfo.create(intent); |
+ if (newWebappInfo == null) { |
+ Log.e(TAG, "Failed to parse new Intent: " + intent); |
+ finish(); |
+ } else if (!TextUtils.equals(mWebappInfo.id(), newWebappInfo.id())) { |
+ mWebappInfo.copy(newWebappInfo); |
+ resetSavedInstanceState(); |
+ if (mIsInitialized) initializeUI(null); |
+ } |
+ } |
+ |
+ private void initializeUI(Bundle savedInstanceState) { |
+ // We do not load URL when restoring from saved instance states. |
+ if (savedInstanceState == null && mWebappInfo.isInitialized()) { |
+ if (TextUtils.isEmpty(getActivityTab().getUrl())) { |
+ getActivityTab().loadUrl(new LoadUrlParams( |
+ mWebappInfo.uri().toString(), PageTransition.AUTO_TOPLEVEL)); |
+ } |
+ } else { |
+ if (NetworkChangeNotifier.isOnline()) getActivityTab().reloadIgnoringCache(); |
+ } |
+ |
+ mWebContentsObserver = createWebContentsObserver(); |
+ getActivityTab().addObserver(createTabObserver()); |
+ updateTaskDescription(); |
+ removeWindowBackground(); |
+ } |
+ |
+ @Override |
+ public void preInflationStartup() { |
+ WebappInfo info = WebappInfo.create(getIntent()); |
+ if (info != null) mWebappInfo.copy(info); |
+ mCleanupTask = new WebappDirectoryManager(getActivityDirectory(), |
+ WEBAPP_SCHEME, FeatureUtilities.isDocumentModeEligible(this)); |
+ |
+ ScreenOrientationProvider.lockOrientation((byte) mWebappInfo.orientation(), this); |
+ super.preInflationStartup(); |
+ } |
+ |
+ @Override |
+ public void finishNativeInitialization() { |
+ if (!mWebappInfo.isInitialized()) finish(); |
+ super.finishNativeInitialization(); |
+ initializeUI(getSavedInstanceState()); |
+ mIsInitialized = true; |
+ } |
+ |
+ @Override |
+ protected void onSaveInstanceState(Bundle outState) { |
+ super.onSaveInstanceState(outState); |
+ mWebappInfo.writeToBundle(outState); |
+ if (getActivityTab() != null) getActivityTab().saveInstanceState(outState); |
+ } |
+ |
+ @Override |
+ public void onStartWithNative() { |
+ super.onStartWithNative(); |
+ if (mCleanupTask.getStatus() == AsyncTask.Status.PENDING) mCleanupTask.execute(); |
+ } |
+ |
+ @Override |
+ public void onStopWithNative() { |
+ super.onStopWithNative(); |
+ mCleanupTask.cancel(true); |
+ if (getActivityTab() != null) getActivityTab().saveState(getActivityDirectory()); |
+ if (getFullscreenManager() != null) { |
+ getFullscreenManager().setPersistentFullscreenMode(false); |
+ } |
+ } |
+ |
+ @Override |
+ public void onResume() { |
+ if (!isFinishing() && getIntent() != null) { |
+ // Avoid situations where Android starts two Activities with the same data. |
+ DocumentUtils.finishOtherTasksWithData(getIntent().getData(), getTaskId()); |
+ } |
+ super.onResume(); |
+ } |
+ @Override |
+ protected int getControlContainerLayoutId() { |
+ return R.layout.webapp_control_container; |
+ } |
+ |
+ @Override |
+ public void postInflationStartup() { |
+ super.postInflationStartup(); |
+ WebappControlContainer controlContainer = |
+ (WebappControlContainer) findViewById(R.id.control_container); |
+ mUrlBar = (WebappUrlBar) controlContainer.findViewById(R.id.webapp_url_bar); |
+ } |
+ |
+ /** |
+ * @return Structure containing data about the webapp currently displayed. |
+ */ |
+ WebappInfo getWebappInfo() { |
+ return mWebappInfo; |
+ } |
+ |
+ private void updateUrlBar() { |
+ Tab tab = getActivityTab(); |
+ if (tab == null || mUrlBar == null) return; |
+ mUrlBar.update(tab.getUrl(), tab.getSecurityLevel()); |
+ } |
+ |
+ private WebContentsObserver createWebContentsObserver() { |
+ // TODO: Move to TabObserver eventually. |
+ return new WebContentsObserver(getActivityTab().getWebContents()) { |
+ @Override |
+ public void didNavigateMainFrame(String url, String baseUrl, |
+ boolean isNavigationToDifferentPage, boolean isNavigationInPage, |
+ int statusCode) { |
+ updateUrlBar(); |
+ } |
+ |
+ @Override |
+ public void didAttachInterstitialPage() { |
+ updateUrlBar(); |
+ |
+ int state = ApplicationStatus.getStateForActivity(WebappActivity.this); |
+ if (state == ActivityState.PAUSED || state == ActivityState.STOPPED |
+ || state == ActivityState.DESTROYED) { |
+ return; |
+ } |
+ |
+ // Kick the interstitial navigation to Chrome. |
+ Intent intent = new Intent( |
+ Intent.ACTION_VIEW, Uri.parse(getActivityTab().getUrl())); |
+ intent.setPackage(getPackageName()); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ startActivity(intent); |
+ |
+ // Pretend like the navigation never happened. We delay so that this happens while |
+ // the Activity is in the background. |
+ mHandler.postDelayed(new Runnable() { |
+ @Override |
+ public void run() { |
+ getActivityTab().goBack(); |
+ } |
+ }, MS_BEFORE_NAVIGATING_BACK_FROM_INTERSTITIAL); |
+ } |
+ |
+ @Override |
+ public void didDetachInterstitialPage() { |
+ updateUrlBar(); |
+ } |
+ }; |
+ } |
+ |
+ private boolean isWebappDomain() { |
+ return UrlUtilities.sameDomainOrHost( |
+ getActivityTab().getUrl(), getWebappInfo().uri().toString(), true); |
+ } |
+ |
+ protected TabObserver createTabObserver() { |
+ return new EmptyTabObserver() { |
+ @Override |
+ public void onSSLStateUpdated(Tab tab) { |
+ updateUrlBar(); |
+ } |
+ |
+ @Override |
+ public void onDidStartProvisionalLoadForFrame( |
+ Tab tab, long frameId, long parentFrameId, boolean isMainFrame, |
+ String validatedUrl, boolean isErrorPage, boolean isIframeSrcdoc) { |
+ if (isMainFrame) updateUrlBar(); |
+ } |
+ |
+ @Override |
+ public void onDidChangeThemeColor(int color) { |
+ if (!isWebappDomain()) return; |
+ mBrandColor = color; |
+ updateTaskDescription(); |
+ } |
+ |
+ @Override |
+ public void onTitleUpdated(Tab tab) { |
+ if (!isWebappDomain()) return; |
+ updateTaskDescription(); |
+ } |
+ |
+ @Override |
+ public void onFaviconUpdated(Tab tab) { |
+ if (!isWebappDomain()) return; |
+ updateTaskDescription(); |
+ } |
+ }; |
+ } |
+ |
+ private void updateTaskDescription() { |
+ String title = mWebappInfo.title() == null |
+ ? getActivityTab().getTitle() : mWebappInfo.title(); |
+ Bitmap icon = mWebappInfo.icon() == null |
+ ? getActivityTab().getFavicon() : mWebappInfo.icon(); |
+ int color = mBrandColor == null |
+ ? getResources().getColor(R.color.default_primary_color) : mBrandColor; |
+ |
+ DocumentUtils.updateTaskDescription(this, title, icon, color, mBrandColor == null); |
+ } |
+ |
+ /** |
+ * Get the active directory by this web app. |
+ * |
+ * @return The directory used for the current web app. |
+ */ |
+ @Override |
+ protected File getActivityDirectory() { |
+ return WebappDirectoryManager.getWebappDirectory(mWebappInfo.id()); |
+ } |
+ |
+ @VisibleForTesting |
+ WebappUrlBar getUrlBarForTests() { |
+ return mUrlBar; |
+ } |
+ |
+ @VisibleForTesting |
+ boolean isUrlBarVisible() { |
+ return findViewById(R.id.control_container).getVisibility() == View.VISIBLE; |
+ } |
+ |
+ @Override |
+ protected final ChromeFullscreenManager createFullscreenManager(View controlContainer) { |
+ return new ChromeFullscreenManager(this, controlContainer, getTabModelSelector(), |
+ getControlContainerHeightResource(), false /* supportsBrowserOverride */); |
+ } |
+ |
+ @Override |
+ protected int getControlContainerHeightResource() { |
+ return R.dimen.webapp_control_container_height; |
+ } |
+ |
+ // Implements {@link FullScreenActivityTab.TopControlsVisibilityDelegate}. |
+ @Override |
+ public boolean shouldShowTopControls(String url, int securityLevel) { |
+ boolean visible = false; // do not show top controls when URL is not ready yet. |
+ if (!TextUtils.isEmpty(url)) { |
+ boolean isSameWebsite = |
+ UrlUtilities.sameDomainOrHost(mWebappInfo.uri().toString(), url, true); |
+ visible = !isSameWebsite |
+ || securityLevel == ConnectionSecurityHelperSecurityLevel.SECURITY_ERROR |
+ || securityLevel == ConnectionSecurityHelperSecurityLevel.SECURITY_WARNING; |
+ } |
+ |
+ return visible; |
+ } |
+ |
+ // We're temporarily disable CS on webapp since there are some issues. (http://crbug.com/471950) |
+ // TODO(changwan): re-enable it once the issues are resolved. |
+ @Override |
+ protected boolean isContextualSearchAllowed() { |
+ return false; |
+ } |
+ |
+ /** |
+ * Launches the URL in its own WebappActivity. |
+ * @param context Context to use for launching the webapp. |
+ * @param id ID of the webapp. |
+ * @param url URL for the webapp. |
+ * @param icon Base64 encoded Bitmap representing the webapp. |
+ * @param title String to show in Recents. |
+ * @param orientation Default orientation for the activity. |
+ */ |
+ public static void launchInstance(Context context, String id, String url, String icon, |
+ String title, int orientation) { |
+ String activityName = WebappActivity.class.getName(); |
+ if (!FeatureUtilities.isDocumentModeEligible(context)) { |
+ // Specifically assign the app to a particular WebappActivity instance. |
+ int activityIndex = ActivityAssigner.instance(context).assign(id); |
+ activityName += String.valueOf(activityIndex); |
+ } |
+ |
+ // Fire an intent to launch the Webapp in an unmapped Activity. |
+ Intent webappIntent = new Intent(); |
+ webappIntent.setClassName(context, activityName); |
+ webappIntent.putExtra(ShortcutHelper.EXTRA_ICON, icon); |
+ webappIntent.putExtra(ShortcutHelper.EXTRA_ID, id); |
+ webappIntent.putExtra(ShortcutHelper.EXTRA_URL, url); |
+ webappIntent.putExtra(ShortcutHelper.EXTRA_TITLE, title); |
+ webappIntent.putExtra(ShortcutHelper.EXTRA_ORIENTATION, orientation); |
+ |
+ // On L, firing intents with the exact same data should relaunch a particular Activity. |
+ webappIntent.setAction(Intent.ACTION_VIEW); |
+ webappIntent.setData(Uri.parse(WEBAPP_SCHEME + "://" + id)); |
+ webappIntent.setFlags(ApiCompatibilityUtils.getActivityNewDocumentFlag()); |
+ |
+ context.startActivity(webappIntent); |
+ } |
+} |