mobile/android/base/webapp/WebappImpl.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko.webapp;
michael@0 7
michael@0 8 import java.io.File;
michael@0 9 import java.io.IOException;
michael@0 10 import java.net.URI;
michael@0 11
michael@0 12 import org.json.JSONException;
michael@0 13 import org.json.JSONObject;
michael@0 14 import org.mozilla.gecko.GeckoApp;
michael@0 15 import org.mozilla.gecko.GeckoAppShell;
michael@0 16 import org.mozilla.gecko.GeckoEvent;
michael@0 17 import org.mozilla.gecko.GeckoThread;
michael@0 18 import org.mozilla.gecko.R;
michael@0 19 import org.mozilla.gecko.Tab;
michael@0 20 import org.mozilla.gecko.Tabs;
michael@0 21 import org.mozilla.gecko.webapp.InstallHelper.InstallCallback;
michael@0 22
michael@0 23 import android.content.Intent;
michael@0 24 import android.content.pm.PackageManager.NameNotFoundException;
michael@0 25 import android.graphics.Color;
michael@0 26 import android.graphics.drawable.Drawable;
michael@0 27 import android.graphics.drawable.GradientDrawable;
michael@0 28 import android.net.Uri;
michael@0 29 import android.os.Bundle;
michael@0 30 import android.util.Log;
michael@0 31 import android.view.Display;
michael@0 32 import android.view.View;
michael@0 33 import android.view.animation.Animation;
michael@0 34 import android.view.animation.AnimationUtils;
michael@0 35 import android.widget.ImageView;
michael@0 36 import android.widget.TextView;
michael@0 37
michael@0 38 public class WebappImpl extends GeckoApp implements InstallCallback {
michael@0 39 private static final String LOGTAG = "GeckoWebappImpl";
michael@0 40
michael@0 41 private URI mOrigin;
michael@0 42 private TextView mTitlebarText = null;
michael@0 43 private View mTitlebar = null;
michael@0 44
michael@0 45 private View mSplashscreen;
michael@0 46
michael@0 47 private boolean mIsApk = true;
michael@0 48 private ApkResources mApkResources;
michael@0 49 private String mManifestUrl;
michael@0 50 private String mAppName;
michael@0 51
michael@0 52 protected int getIndex() { return 0; }
michael@0 53
michael@0 54 @Override
michael@0 55 public int getLayout() { return R.layout.web_app; }
michael@0 56
michael@0 57 @Override
michael@0 58 public boolean hasTabsSideBar() { return false; }
michael@0 59
michael@0 60 @Override
michael@0 61 public void onCreate(Bundle savedInstance)
michael@0 62 {
michael@0 63
michael@0 64 String action = getIntent().getAction();
michael@0 65 Bundle extras = getIntent().getExtras();
michael@0 66 if (extras == null) {
michael@0 67 extras = savedInstance;
michael@0 68 }
michael@0 69
michael@0 70 if (extras == null) {
michael@0 71 extras = new Bundle();
michael@0 72 }
michael@0 73
michael@0 74 boolean isInstalled = extras.getBoolean("isInstalled", false);
michael@0 75 String packageName = extras.getString("packageName");
michael@0 76
michael@0 77 if (packageName == null) {
michael@0 78 Log.w(LOGTAG, "no package name; treating as legacy shortcut");
michael@0 79
michael@0 80 mIsApk = false;
michael@0 81
michael@0 82 // Shortcut apps are already installed.
michael@0 83 isInstalled = true;
michael@0 84
michael@0 85 Uri data = getIntent().getData();
michael@0 86 if (data == null) {
michael@0 87 Log.wtf(LOGTAG, "can't get manifest URL from shortcut data");
michael@0 88 setResult(RESULT_CANCELED);
michael@0 89 finish();
michael@0 90 return;
michael@0 91 }
michael@0 92 mManifestUrl = data.toString();
michael@0 93
michael@0 94 String shortcutName = extras.getString(Intent.EXTRA_SHORTCUT_NAME);
michael@0 95 mAppName = shortcutName != null ? shortcutName : "Web App";
michael@0 96 } else {
michael@0 97 try {
michael@0 98 mApkResources = new ApkResources(this, packageName);
michael@0 99 } catch (NameNotFoundException e) {
michael@0 100 Log.e(LOGTAG, "Can't find package for webapp " + packageName, e);
michael@0 101 setResult(RESULT_CANCELED);
michael@0 102 finish();
michael@0 103 return;
michael@0 104 }
michael@0 105
michael@0 106 mManifestUrl = mApkResources.getManifestUrl();
michael@0 107 mAppName = mApkResources.getAppName();
michael@0 108 }
michael@0 109
michael@0 110 // start Gecko.
michael@0 111 super.onCreate(savedInstance);
michael@0 112
michael@0 113 mTitlebarText = (TextView)findViewById(R.id.webapp_title);
michael@0 114 mTitlebar = findViewById(R.id.webapp_titlebar);
michael@0 115 mSplashscreen = findViewById(R.id.splashscreen);
michael@0 116
michael@0 117 Allocator allocator = Allocator.getInstance(this);
michael@0 118 int index = getIndex();
michael@0 119
michael@0 120 // We have to migrate old prefs before getting the origin because origin
michael@0 121 // is one of the prefs we might migrate.
michael@0 122 allocator.maybeMigrateOldPrefs(index);
michael@0 123
michael@0 124 String origin = allocator.getOrigin(index);
michael@0 125 boolean isInstallCompleting = (origin == null);
michael@0 126
michael@0 127 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning) || !isInstalled || isInstallCompleting) {
michael@0 128 // Show the splash screen if we need to start Gecko, or we need to install this.
michael@0 129 overridePendingTransition(R.anim.grow_fade_in_center, android.R.anim.fade_out);
michael@0 130 showSplash();
michael@0 131 } else {
michael@0 132 mSplashscreen.setVisibility(View.GONE);
michael@0 133 }
michael@0 134
michael@0 135 if (!isInstalled || isInstallCompleting) {
michael@0 136 InstallHelper installHelper = new InstallHelper(getApplicationContext(), mApkResources, this);
michael@0 137 if (!isInstalled) {
michael@0 138 // start the vanilla install.
michael@0 139 try {
michael@0 140 installHelper.startInstall(getDefaultProfileName());
michael@0 141 } catch (IOException e) {
michael@0 142 Log.e(LOGTAG, "Couldn't install packaged app", e);
michael@0 143 }
michael@0 144 } else {
michael@0 145 // an install is already happening, so we should let it complete.
michael@0 146 Log.i(LOGTAG, "Waiting for existing install to complete");
michael@0 147 installHelper.registerGeckoListener();
michael@0 148 }
michael@0 149 return;
michael@0 150 } else {
michael@0 151 launchWebapp(origin);
michael@0 152 }
michael@0 153
michael@0 154 setTitle(mAppName);
michael@0 155 }
michael@0 156
michael@0 157 @Override
michael@0 158 protected String getURIFromIntent(Intent intent) {
michael@0 159 String uri = super.getURIFromIntent(intent);
michael@0 160 if (uri != null) {
michael@0 161 return uri;
michael@0 162 }
michael@0 163 // This is where we construct the URL from the Intent from the
michael@0 164 // the synthesized APK.
michael@0 165
michael@0 166 // TODO Translate AndroidIntents into WebActivities here.
michael@0 167 if (mIsApk) {
michael@0 168 return mApkResources.getManifestUrl();
michael@0 169 }
michael@0 170
michael@0 171 // If this is a legacy shortcut, then we should have been able to get
michael@0 172 // the URI from the intent data. Otherwise, we should have been able
michael@0 173 // to get it from the APK resources. So we should never get here.
michael@0 174 Log.wtf(LOGTAG, "Couldn't get URI from intent nor APK resources");
michael@0 175 return null;
michael@0 176 }
michael@0 177
michael@0 178 @Override
michael@0 179 protected void loadStartupTab(String uri) {
michael@0 180 // Load a tab so it's available for any code that assumes a tab
michael@0 181 // before the app tab itself is loaded in BrowserApp._loadWebapp.
michael@0 182 super.loadStartupTab("about:blank");
michael@0 183 }
michael@0 184
michael@0 185 private void showSplash() {
michael@0 186
michael@0 187 // get the favicon dominant color, stored when the app was installed
michael@0 188 int dominantColor = Allocator.getInstance().getColor(getIndex());
michael@0 189
michael@0 190 setBackgroundGradient(dominantColor);
michael@0 191
michael@0 192 ImageView image = (ImageView)findViewById(R.id.splashscreen_icon);
michael@0 193 Drawable d = null;
michael@0 194
michael@0 195 if (mIsApk) {
michael@0 196 Uri uri = mApkResources.getAppIconUri();
michael@0 197 image.setImageURI(uri);
michael@0 198 d = image.getDrawable();
michael@0 199 } else {
michael@0 200 // look for a logo.png in the profile dir and show it. If we can't find a logo show nothing
michael@0 201 File profile = getProfile().getDir();
michael@0 202 File logoFile = new File(profile, "logo.png");
michael@0 203 if (logoFile.exists()) {
michael@0 204 d = Drawable.createFromPath(logoFile.getPath());
michael@0 205 image.setImageDrawable(d);
michael@0 206 }
michael@0 207 }
michael@0 208
michael@0 209 if (d != null) {
michael@0 210 Animation fadein = AnimationUtils.loadAnimation(this, R.anim.grow_fade_in_center);
michael@0 211 fadein.setStartOffset(500);
michael@0 212 fadein.setDuration(1000);
michael@0 213 image.startAnimation(fadein);
michael@0 214 }
michael@0 215 }
michael@0 216
michael@0 217 public void setBackgroundGradient(int dominantColor) {
michael@0 218 int[] colors = new int[2];
michael@0 219 // now lighten it, to ensure that the icon stands out in the center
michael@0 220 float[] f = new float[3];
michael@0 221 Color.colorToHSV(dominantColor, f);
michael@0 222 f[2] = Math.min(f[2]*2, 1.0f);
michael@0 223 colors[0] = Color.HSVToColor(255, f);
michael@0 224
michael@0 225 // now generate a second, slightly darker version of the same color
michael@0 226 f[2] *= 0.75;
michael@0 227 colors[1] = Color.HSVToColor(255, f);
michael@0 228
michael@0 229 // Draw the background gradient
michael@0 230 GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TL_BR, colors);
michael@0 231 gd.setGradientType(GradientDrawable.RADIAL_GRADIENT);
michael@0 232 Display display = getWindowManager().getDefaultDisplay();
michael@0 233 gd.setGradientCenter(0.5f, 0.5f);
michael@0 234 gd.setGradientRadius(Math.max(display.getWidth()/2, display.getHeight()/2));
michael@0 235 mSplashscreen.setBackgroundDrawable(gd);
michael@0 236 }
michael@0 237
michael@0 238 /* (non-Javadoc)
michael@0 239 * @see org.mozilla.gecko.GeckoApp#getDefaultProfileName()
michael@0 240 */
michael@0 241 @Override
michael@0 242 protected String getDefaultProfileName() {
michael@0 243 return "webapp" + getIndex();
michael@0 244 }
michael@0 245
michael@0 246 @Override
michael@0 247 protected boolean getSessionRestoreState(Bundle savedInstanceState) {
michael@0 248 // for now webapps never restore your session
michael@0 249 return false;
michael@0 250 }
michael@0 251
michael@0 252 @Override
michael@0 253 public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
michael@0 254 switch(msg) {
michael@0 255 case SELECTED:
michael@0 256 case LOCATION_CHANGE:
michael@0 257 if (Tabs.getInstance().isSelectedTab(tab)) {
michael@0 258 final String urlString = tab.getURL();
michael@0 259
michael@0 260 // Don't show the titlebar for about:blank, which we load
michael@0 261 // into the initial tab we create while waiting for the app
michael@0 262 // to load.
michael@0 263 if (urlString != null && urlString.equals("about:blank")) {
michael@0 264 mTitlebar.setVisibility(View.GONE);
michael@0 265 return;
michael@0 266 }
michael@0 267
michael@0 268 final URI uri;
michael@0 269
michael@0 270 try {
michael@0 271 uri = new URI(urlString);
michael@0 272 } catch (java.net.URISyntaxException ex) {
michael@0 273 mTitlebarText.setText(urlString);
michael@0 274
michael@0 275 // If we can't parse the url, and its an app protocol hide
michael@0 276 // the titlebar and return, otherwise show the titlebar
michael@0 277 // and the full url
michael@0 278 if (urlString != null && !urlString.startsWith("app://")) {
michael@0 279 mTitlebar.setVisibility(View.VISIBLE);
michael@0 280 } else {
michael@0 281 mTitlebar.setVisibility(View.GONE);
michael@0 282 }
michael@0 283 return;
michael@0 284 }
michael@0 285
michael@0 286 if (mOrigin != null && mOrigin.getHost().equals(uri.getHost())) {
michael@0 287 mTitlebar.setVisibility(View.GONE);
michael@0 288 } else {
michael@0 289 mTitlebarText.setText(uri.getScheme() + "://" + uri.getHost());
michael@0 290 mTitlebar.setVisibility(View.VISIBLE);
michael@0 291 }
michael@0 292 }
michael@0 293 break;
michael@0 294 case LOADED:
michael@0 295 hideSplash();
michael@0 296 break;
michael@0 297 case START:
michael@0 298 if (mSplashscreen != null && mSplashscreen.getVisibility() == View.VISIBLE) {
michael@0 299 View area = findViewById(R.id.splashscreen_progress);
michael@0 300 area.setVisibility(View.VISIBLE);
michael@0 301 Animation fadein = AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
michael@0 302 fadein.setDuration(1000);
michael@0 303 area.startAnimation(fadein);
michael@0 304 }
michael@0 305 break;
michael@0 306 }
michael@0 307 super.onTabChanged(tab, msg, data);
michael@0 308 }
michael@0 309
michael@0 310 protected void hideSplash() {
michael@0 311 if (mSplashscreen != null && mSplashscreen.getVisibility() == View.VISIBLE) {
michael@0 312 Animation fadeout = AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
michael@0 313 fadeout.setAnimationListener(new Animation.AnimationListener() {
michael@0 314 @Override
michael@0 315 public void onAnimationEnd(Animation animation) {
michael@0 316 mSplashscreen.setVisibility(View.GONE);
michael@0 317 }
michael@0 318 @Override
michael@0 319 public void onAnimationRepeat(Animation animation) { }
michael@0 320 @Override
michael@0 321 public void onAnimationStart(Animation animation) { }
michael@0 322 });
michael@0 323 mSplashscreen.startAnimation(fadeout);
michael@0 324 }
michael@0 325 }
michael@0 326
michael@0 327 @Override
michael@0 328 public void installCompleted(InstallHelper installHelper, String event, JSONObject message) {
michael@0 329 if (event == null) {
michael@0 330 return;
michael@0 331 }
michael@0 332
michael@0 333 if (event.equals("Webapps:Postinstall")) {
michael@0 334 String origin = message.optString("origin");
michael@0 335 launchWebapp(origin);
michael@0 336 }
michael@0 337 }
michael@0 338
michael@0 339 @Override
michael@0 340 public void installErrored(InstallHelper installHelper, Exception exception) {
michael@0 341 Log.e(LOGTAG, "Install errored", exception);
michael@0 342 }
michael@0 343
michael@0 344 private void setOrigin(String origin) {
michael@0 345 try {
michael@0 346 mOrigin = new URI(origin);
michael@0 347 } catch (java.net.URISyntaxException ex) {
michael@0 348 // If this isn't an app: URL, just settle for not having an origin.
michael@0 349 if (!origin.startsWith("app://")) {
michael@0 350 return;
michael@0 351 }
michael@0 352
michael@0 353 // If that failed fall back to the origin stored in the shortcut.
michael@0 354 if (!mIsApk) {
michael@0 355 Log.i(LOGTAG, "Origin is app: URL; falling back to intent URL");
michael@0 356 Uri data = getIntent().getData();
michael@0 357 if (data != null) {
michael@0 358 try {
michael@0 359 mOrigin = new URI(data.toString());
michael@0 360 } catch (java.net.URISyntaxException ex2) {
michael@0 361 Log.e(LOGTAG, "Unable to parse intent URL: ", ex);
michael@0 362 }
michael@0 363 }
michael@0 364 }
michael@0 365 }
michael@0 366 }
michael@0 367
michael@0 368 public void launchWebapp(String origin) {
michael@0 369 setOrigin(origin);
michael@0 370
michael@0 371 try {
michael@0 372 JSONObject launchObject = new JSONObject();
michael@0 373 launchObject.putOpt("url", mManifestUrl);
michael@0 374 launchObject.putOpt("name", mAppName);
michael@0 375 Log.i(LOGTAG, "Trying to launch: " + launchObject);
michael@0 376 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Webapps:Load", launchObject.toString()));
michael@0 377 } catch (JSONException e) {
michael@0 378 Log.e(LOGTAG, "Error populating launch message", e);
michael@0 379 }
michael@0 380 }
michael@0 381
michael@0 382 @Override
michael@0 383 protected boolean getIsDebuggable() {
michael@0 384 if (mIsApk) {
michael@0 385 return mApkResources.isDebuggable();
michael@0 386 }
michael@0 387
michael@0 388 // This is a legacy shortcut, which didn't provide a way to determine
michael@0 389 // that the app is debuggable, so we say the app is not debuggable.
michael@0 390 return false;
michael@0 391 }
michael@0 392 }

mercurial