OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 |
| 5 package org.chromium.chrome.browser.webapps; |
| 6 |
| 7 import android.content.Context; |
| 8 import android.content.Intent; |
| 9 import android.graphics.Bitmap; |
| 10 import android.net.Uri; |
| 11 import android.os.AsyncTask; |
| 12 import android.os.Bundle; |
| 13 import android.text.TextUtils; |
| 14 import android.util.Log; |
| 15 import android.view.View; |
| 16 |
| 17 import com.google.android.apps.chrome.R; |
| 18 |
| 19 import org.chromium.base.ActivityState; |
| 20 import org.chromium.base.ApiCompatibilityUtils; |
| 21 import org.chromium.base.ApplicationStatus; |
| 22 import org.chromium.base.VisibleForTesting; |
| 23 import org.chromium.chrome.browser.EmptyTabObserver; |
| 24 import org.chromium.chrome.browser.ShortcutHelper; |
| 25 import org.chromium.chrome.browser.Tab; |
| 26 import org.chromium.chrome.browser.TabObserver; |
| 27 import org.chromium.chrome.browser.UrlUtilities; |
| 28 import org.chromium.chrome.browser.document.DocumentUtils; |
| 29 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
| 30 import org.chromium.chrome.browser.ssl.ConnectionSecurityHelperSecurityLevel; |
| 31 import org.chromium.chrome.browser.util.FeatureUtilities; |
| 32 import org.chromium.content.browser.ScreenOrientationProvider; |
| 33 import org.chromium.content_public.browser.LoadUrlParams; |
| 34 import org.chromium.content_public.browser.WebContentsObserver; |
| 35 import org.chromium.net.NetworkChangeNotifier; |
| 36 import org.chromium.ui.base.PageTransition; |
| 37 |
| 38 import java.io.File; |
| 39 |
| 40 /** |
| 41 * Displays a webapp in a nearly UI-less Chrome (InfoBars still appear). |
| 42 */ |
| 43 public class WebappActivity extends FullScreenActivity { |
| 44 public static final String WEBAPP_SCHEME = "webapp"; |
| 45 |
| 46 private static final String TAG = "WebappActivity"; |
| 47 private static final long MS_BEFORE_NAVIGATING_BACK_FROM_INTERSTITIAL = 1000
; |
| 48 |
| 49 private final WebappInfo mWebappInfo; |
| 50 private AsyncTask<Void, Void, Void> mCleanupTask; |
| 51 |
| 52 private WebContentsObserver mWebContentsObserver; |
| 53 |
| 54 private WebappUrlBar mUrlBar; |
| 55 |
| 56 private boolean mIsInitialized; |
| 57 private Integer mBrandColor; |
| 58 |
| 59 /** |
| 60 * Construct all the variables that shouldn't change. We do it here both to
clarify when the |
| 61 * objects are created and to ensure that they exist throughout the parallel
ized initialization |
| 62 * of the WebappActivity. |
| 63 */ |
| 64 public WebappActivity() { |
| 65 mWebappInfo = WebappInfo.createEmpty(); |
| 66 } |
| 67 |
| 68 @Override |
| 69 protected void onNewIntent(Intent intent) { |
| 70 if (intent == null) return; |
| 71 super.onNewIntent(intent); |
| 72 |
| 73 WebappInfo newWebappInfo = WebappInfo.create(intent); |
| 74 if (newWebappInfo == null) { |
| 75 Log.e(TAG, "Failed to parse new Intent: " + intent); |
| 76 finish(); |
| 77 } else if (!TextUtils.equals(mWebappInfo.id(), newWebappInfo.id())) { |
| 78 mWebappInfo.copy(newWebappInfo); |
| 79 resetSavedInstanceState(); |
| 80 if (mIsInitialized) initializeUI(null); |
| 81 } |
| 82 } |
| 83 |
| 84 private void initializeUI(Bundle savedInstanceState) { |
| 85 // We do not load URL when restoring from saved instance states. |
| 86 if (savedInstanceState == null && mWebappInfo.isInitialized()) { |
| 87 if (TextUtils.isEmpty(getActivityTab().getUrl())) { |
| 88 getActivityTab().loadUrl(new LoadUrlParams( |
| 89 mWebappInfo.uri().toString(), PageTransition.AUTO_TOPLEV
EL)); |
| 90 } |
| 91 } else { |
| 92 if (NetworkChangeNotifier.isOnline()) getActivityTab().reloadIgnorin
gCache(); |
| 93 } |
| 94 |
| 95 mWebContentsObserver = createWebContentsObserver(); |
| 96 getActivityTab().addObserver(createTabObserver()); |
| 97 updateTaskDescription(); |
| 98 removeWindowBackground(); |
| 99 } |
| 100 |
| 101 @Override |
| 102 public void preInflationStartup() { |
| 103 WebappInfo info = WebappInfo.create(getIntent()); |
| 104 if (info != null) mWebappInfo.copy(info); |
| 105 mCleanupTask = new WebappDirectoryManager(getActivityDirectory(), |
| 106 WEBAPP_SCHEME, FeatureUtilities.isDocumentModeEligible(this)); |
| 107 |
| 108 ScreenOrientationProvider.lockOrientation((byte) mWebappInfo.orientation
(), this); |
| 109 super.preInflationStartup(); |
| 110 } |
| 111 |
| 112 @Override |
| 113 public void finishNativeInitialization() { |
| 114 if (!mWebappInfo.isInitialized()) finish(); |
| 115 super.finishNativeInitialization(); |
| 116 initializeUI(getSavedInstanceState()); |
| 117 mIsInitialized = true; |
| 118 } |
| 119 |
| 120 @Override |
| 121 protected void onSaveInstanceState(Bundle outState) { |
| 122 super.onSaveInstanceState(outState); |
| 123 mWebappInfo.writeToBundle(outState); |
| 124 if (getActivityTab() != null) getActivityTab().saveInstanceState(outStat
e); |
| 125 } |
| 126 |
| 127 @Override |
| 128 public void onStartWithNative() { |
| 129 super.onStartWithNative(); |
| 130 if (mCleanupTask.getStatus() == AsyncTask.Status.PENDING) mCleanupTask.e
xecute(); |
| 131 } |
| 132 |
| 133 @Override |
| 134 public void onStopWithNative() { |
| 135 super.onStopWithNative(); |
| 136 mCleanupTask.cancel(true); |
| 137 if (getActivityTab() != null) getActivityTab().saveState(getActivityDire
ctory()); |
| 138 if (getFullscreenManager() != null) { |
| 139 getFullscreenManager().setPersistentFullscreenMode(false); |
| 140 } |
| 141 } |
| 142 |
| 143 @Override |
| 144 public void onResume() { |
| 145 if (!isFinishing() && getIntent() != null) { |
| 146 // Avoid situations where Android starts two Activities with the sam
e data. |
| 147 DocumentUtils.finishOtherTasksWithData(getIntent().getData(), getTas
kId()); |
| 148 } |
| 149 super.onResume(); |
| 150 } |
| 151 @Override |
| 152 protected int getControlContainerLayoutId() { |
| 153 return R.layout.webapp_control_container; |
| 154 } |
| 155 |
| 156 @Override |
| 157 public void postInflationStartup() { |
| 158 super.postInflationStartup(); |
| 159 WebappControlContainer controlContainer = |
| 160 (WebappControlContainer) findViewById(R.id.control_container); |
| 161 mUrlBar = (WebappUrlBar) controlContainer.findViewById(R.id.webapp_url_b
ar); |
| 162 } |
| 163 |
| 164 /** |
| 165 * @return Structure containing data about the webapp currently displayed. |
| 166 */ |
| 167 WebappInfo getWebappInfo() { |
| 168 return mWebappInfo; |
| 169 } |
| 170 |
| 171 private void updateUrlBar() { |
| 172 Tab tab = getActivityTab(); |
| 173 if (tab == null || mUrlBar == null) return; |
| 174 mUrlBar.update(tab.getUrl(), tab.getSecurityLevel()); |
| 175 } |
| 176 |
| 177 private WebContentsObserver createWebContentsObserver() { |
| 178 // TODO: Move to TabObserver eventually. |
| 179 return new WebContentsObserver(getActivityTab().getWebContents()) { |
| 180 @Override |
| 181 public void didNavigateMainFrame(String url, String baseUrl, |
| 182 boolean isNavigationToDifferentPage, boolean isNavigationInP
age, |
| 183 int statusCode) { |
| 184 updateUrlBar(); |
| 185 } |
| 186 |
| 187 @Override |
| 188 public void didAttachInterstitialPage() { |
| 189 updateUrlBar(); |
| 190 |
| 191 int state = ApplicationStatus.getStateForActivity(WebappActivity
.this); |
| 192 if (state == ActivityState.PAUSED || state == ActivityState.STOP
PED |
| 193 || state == ActivityState.DESTROYED) { |
| 194 return; |
| 195 } |
| 196 |
| 197 // Kick the interstitial navigation to Chrome. |
| 198 Intent intent = new Intent( |
| 199 Intent.ACTION_VIEW, Uri.parse(getActivityTab().getUrl())
); |
| 200 intent.setPackage(getPackageName()); |
| 201 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 202 startActivity(intent); |
| 203 |
| 204 // Pretend like the navigation never happened. We delay so that
this happens while |
| 205 // the Activity is in the background. |
| 206 mHandler.postDelayed(new Runnable() { |
| 207 @Override |
| 208 public void run() { |
| 209 getActivityTab().goBack(); |
| 210 } |
| 211 }, MS_BEFORE_NAVIGATING_BACK_FROM_INTERSTITIAL); |
| 212 } |
| 213 |
| 214 @Override |
| 215 public void didDetachInterstitialPage() { |
| 216 updateUrlBar(); |
| 217 } |
| 218 }; |
| 219 } |
| 220 |
| 221 private boolean isWebappDomain() { |
| 222 return UrlUtilities.sameDomainOrHost( |
| 223 getActivityTab().getUrl(), getWebappInfo().uri().toString(), tru
e); |
| 224 } |
| 225 |
| 226 protected TabObserver createTabObserver() { |
| 227 return new EmptyTabObserver() { |
| 228 @Override |
| 229 public void onSSLStateUpdated(Tab tab) { |
| 230 updateUrlBar(); |
| 231 } |
| 232 |
| 233 @Override |
| 234 public void onDidStartProvisionalLoadForFrame( |
| 235 Tab tab, long frameId, long parentFrameId, boolean isMainFra
me, |
| 236 String validatedUrl, boolean isErrorPage, boolean isIframeSr
cdoc) { |
| 237 if (isMainFrame) updateUrlBar(); |
| 238 } |
| 239 |
| 240 @Override |
| 241 public void onDidChangeThemeColor(int color) { |
| 242 if (!isWebappDomain()) return; |
| 243 mBrandColor = color; |
| 244 updateTaskDescription(); |
| 245 } |
| 246 |
| 247 @Override |
| 248 public void onTitleUpdated(Tab tab) { |
| 249 if (!isWebappDomain()) return; |
| 250 updateTaskDescription(); |
| 251 } |
| 252 |
| 253 @Override |
| 254 public void onFaviconUpdated(Tab tab) { |
| 255 if (!isWebappDomain()) return; |
| 256 updateTaskDescription(); |
| 257 } |
| 258 }; |
| 259 } |
| 260 |
| 261 private void updateTaskDescription() { |
| 262 String title = mWebappInfo.title() == null |
| 263 ? getActivityTab().getTitle() : mWebappInfo.title(); |
| 264 Bitmap icon = mWebappInfo.icon() == null |
| 265 ? getActivityTab().getFavicon() : mWebappInfo.icon(); |
| 266 int color = mBrandColor == null |
| 267 ? getResources().getColor(R.color.default_primary_color) : mBran
dColor; |
| 268 |
| 269 DocumentUtils.updateTaskDescription(this, title, icon, color, mBrandColo
r == null); |
| 270 } |
| 271 |
| 272 /** |
| 273 * Get the active directory by this web app. |
| 274 * |
| 275 * @return The directory used for the current web app. |
| 276 */ |
| 277 @Override |
| 278 protected File getActivityDirectory() { |
| 279 return WebappDirectoryManager.getWebappDirectory(mWebappInfo.id()); |
| 280 } |
| 281 |
| 282 @VisibleForTesting |
| 283 WebappUrlBar getUrlBarForTests() { |
| 284 return mUrlBar; |
| 285 } |
| 286 |
| 287 @VisibleForTesting |
| 288 boolean isUrlBarVisible() { |
| 289 return findViewById(R.id.control_container).getVisibility() == View.VISI
BLE; |
| 290 } |
| 291 |
| 292 @Override |
| 293 protected final ChromeFullscreenManager createFullscreenManager(View control
Container) { |
| 294 return new ChromeFullscreenManager(this, controlContainer, getTabModelSe
lector(), |
| 295 getControlContainerHeightResource(), false /* supportsBrowserOve
rride */); |
| 296 } |
| 297 |
| 298 @Override |
| 299 protected int getControlContainerHeightResource() { |
| 300 return R.dimen.webapp_control_container_height; |
| 301 } |
| 302 |
| 303 // Implements {@link FullScreenActivityTab.TopControlsVisibilityDelegate}. |
| 304 @Override |
| 305 public boolean shouldShowTopControls(String url, int securityLevel) { |
| 306 boolean visible = false; // do not show top controls when URL is not re
ady yet. |
| 307 if (!TextUtils.isEmpty(url)) { |
| 308 boolean isSameWebsite = |
| 309 UrlUtilities.sameDomainOrHost(mWebappInfo.uri().toString(),
url, true); |
| 310 visible = !isSameWebsite |
| 311 || securityLevel == ConnectionSecurityHelperSecurityLevel.SE
CURITY_ERROR |
| 312 || securityLevel == ConnectionSecurityHelperSecurityLevel.SE
CURITY_WARNING; |
| 313 } |
| 314 |
| 315 return visible; |
| 316 } |
| 317 |
| 318 // We're temporarily disable CS on webapp since there are some issues. (http
://crbug.com/471950) |
| 319 // TODO(changwan): re-enable it once the issues are resolved. |
| 320 @Override |
| 321 protected boolean isContextualSearchAllowed() { |
| 322 return false; |
| 323 } |
| 324 |
| 325 /** |
| 326 * Launches the URL in its own WebappActivity. |
| 327 * @param context Context to use for launching the webapp. |
| 328 * @param id ID of the webapp. |
| 329 * @param url URL for the webapp. |
| 330 * @param icon Base64 encoded Bitmap representing the webapp. |
| 331 * @param title String to show in Recents. |
| 332 * @param orientation Default orientation for the activity. |
| 333 */ |
| 334 public static void launchInstance(Context context, String id, String url, St
ring icon, |
| 335 String title, int orientation) { |
| 336 String activityName = WebappActivity.class.getName(); |
| 337 if (!FeatureUtilities.isDocumentModeEligible(context)) { |
| 338 // Specifically assign the app to a particular WebappActivity instan
ce. |
| 339 int activityIndex = ActivityAssigner.instance(context).assign(id); |
| 340 activityName += String.valueOf(activityIndex); |
| 341 } |
| 342 |
| 343 // Fire an intent to launch the Webapp in an unmapped Activity. |
| 344 Intent webappIntent = new Intent(); |
| 345 webappIntent.setClassName(context, activityName); |
| 346 webappIntent.putExtra(ShortcutHelper.EXTRA_ICON, icon); |
| 347 webappIntent.putExtra(ShortcutHelper.EXTRA_ID, id); |
| 348 webappIntent.putExtra(ShortcutHelper.EXTRA_URL, url); |
| 349 webappIntent.putExtra(ShortcutHelper.EXTRA_TITLE, title); |
| 350 webappIntent.putExtra(ShortcutHelper.EXTRA_ORIENTATION, orientation); |
| 351 |
| 352 // On L, firing intents with the exact same data should relaunch a parti
cular Activity. |
| 353 webappIntent.setAction(Intent.ACTION_VIEW); |
| 354 webappIntent.setData(Uri.parse(WEBAPP_SCHEME + "://" + id)); |
| 355 webappIntent.setFlags(ApiCompatibilityUtils.getActivityNewDocumentFlag()
); |
| 356 |
| 357 context.startActivity(webappIntent); |
| 358 } |
| 359 } |
OLD | NEW |