mobile/android/base/webapp/WebappImpl.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/webapp/WebappImpl.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,392 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko.webapp;
    1.10 +
    1.11 +import java.io.File;
    1.12 +import java.io.IOException;
    1.13 +import java.net.URI;
    1.14 +
    1.15 +import org.json.JSONException;
    1.16 +import org.json.JSONObject;
    1.17 +import org.mozilla.gecko.GeckoApp;
    1.18 +import org.mozilla.gecko.GeckoAppShell;
    1.19 +import org.mozilla.gecko.GeckoEvent;
    1.20 +import org.mozilla.gecko.GeckoThread;
    1.21 +import org.mozilla.gecko.R;
    1.22 +import org.mozilla.gecko.Tab;
    1.23 +import org.mozilla.gecko.Tabs;
    1.24 +import org.mozilla.gecko.webapp.InstallHelper.InstallCallback;
    1.25 +
    1.26 +import android.content.Intent;
    1.27 +import android.content.pm.PackageManager.NameNotFoundException;
    1.28 +import android.graphics.Color;
    1.29 +import android.graphics.drawable.Drawable;
    1.30 +import android.graphics.drawable.GradientDrawable;
    1.31 +import android.net.Uri;
    1.32 +import android.os.Bundle;
    1.33 +import android.util.Log;
    1.34 +import android.view.Display;
    1.35 +import android.view.View;
    1.36 +import android.view.animation.Animation;
    1.37 +import android.view.animation.AnimationUtils;
    1.38 +import android.widget.ImageView;
    1.39 +import android.widget.TextView;
    1.40 +
    1.41 +public class WebappImpl extends GeckoApp implements InstallCallback {
    1.42 +    private static final String LOGTAG = "GeckoWebappImpl";
    1.43 +
    1.44 +    private URI mOrigin;
    1.45 +    private TextView mTitlebarText = null;
    1.46 +    private View mTitlebar = null;
    1.47 +
    1.48 +    private View mSplashscreen;
    1.49 +
    1.50 +    private boolean mIsApk = true;
    1.51 +    private ApkResources mApkResources;
    1.52 +    private String mManifestUrl;
    1.53 +    private String mAppName;
    1.54 +
    1.55 +    protected int getIndex() { return 0; }
    1.56 +
    1.57 +    @Override
    1.58 +    public int getLayout() { return R.layout.web_app; }
    1.59 +
    1.60 +    @Override
    1.61 +    public boolean hasTabsSideBar() { return false; }
    1.62 +
    1.63 +    @Override
    1.64 +    public void onCreate(Bundle savedInstance)
    1.65 +    {
    1.66 +
    1.67 +        String action = getIntent().getAction();
    1.68 +        Bundle extras = getIntent().getExtras();
    1.69 +        if (extras == null) {
    1.70 +            extras = savedInstance;
    1.71 +        }
    1.72 +
    1.73 +        if (extras == null) {
    1.74 +            extras = new Bundle();
    1.75 +        }
    1.76 +
    1.77 +        boolean isInstalled = extras.getBoolean("isInstalled", false);
    1.78 +        String packageName = extras.getString("packageName");
    1.79 +
    1.80 +        if (packageName == null) {
    1.81 +            Log.w(LOGTAG, "no package name; treating as legacy shortcut");
    1.82 +
    1.83 +            mIsApk = false;
    1.84 +
    1.85 +            // Shortcut apps are already installed.
    1.86 +            isInstalled = true;
    1.87 +
    1.88 +            Uri data = getIntent().getData();
    1.89 +            if (data == null) {
    1.90 +                Log.wtf(LOGTAG, "can't get manifest URL from shortcut data");
    1.91 +                setResult(RESULT_CANCELED);
    1.92 +                finish();
    1.93 +                return;
    1.94 +            }
    1.95 +            mManifestUrl = data.toString();
    1.96 +
    1.97 +            String shortcutName = extras.getString(Intent.EXTRA_SHORTCUT_NAME);
    1.98 +            mAppName = shortcutName != null ? shortcutName : "Web App";
    1.99 +        } else {
   1.100 +            try {
   1.101 +                mApkResources = new ApkResources(this, packageName);
   1.102 +            } catch (NameNotFoundException e) {
   1.103 +                Log.e(LOGTAG, "Can't find package for webapp " + packageName, e);
   1.104 +                setResult(RESULT_CANCELED);
   1.105 +                finish();
   1.106 +                return;
   1.107 +            }
   1.108 +
   1.109 +            mManifestUrl = mApkResources.getManifestUrl();
   1.110 +            mAppName = mApkResources.getAppName();
   1.111 +        }
   1.112 +
   1.113 +        // start Gecko.
   1.114 +        super.onCreate(savedInstance);
   1.115 +
   1.116 +        mTitlebarText = (TextView)findViewById(R.id.webapp_title);
   1.117 +        mTitlebar = findViewById(R.id.webapp_titlebar);
   1.118 +        mSplashscreen = findViewById(R.id.splashscreen);
   1.119 +
   1.120 +        Allocator allocator = Allocator.getInstance(this);
   1.121 +        int index = getIndex();
   1.122 +
   1.123 +        // We have to migrate old prefs before getting the origin because origin
   1.124 +        // is one of the prefs we might migrate.
   1.125 +        allocator.maybeMigrateOldPrefs(index);
   1.126 +
   1.127 +        String origin = allocator.getOrigin(index);
   1.128 +        boolean isInstallCompleting = (origin == null);
   1.129 +
   1.130 +        if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning) || !isInstalled || isInstallCompleting) {
   1.131 +            // Show the splash screen if we need to start Gecko, or we need to install this.
   1.132 +            overridePendingTransition(R.anim.grow_fade_in_center, android.R.anim.fade_out);
   1.133 +            showSplash();
   1.134 +        } else {
   1.135 +            mSplashscreen.setVisibility(View.GONE);
   1.136 +        }
   1.137 +
   1.138 +        if (!isInstalled || isInstallCompleting) {
   1.139 +            InstallHelper installHelper = new InstallHelper(getApplicationContext(), mApkResources, this);
   1.140 +            if (!isInstalled) {
   1.141 +                // start the vanilla install.
   1.142 +                try {
   1.143 +                    installHelper.startInstall(getDefaultProfileName());
   1.144 +                } catch (IOException e) {
   1.145 +                    Log.e(LOGTAG, "Couldn't install packaged app", e);
   1.146 +                }
   1.147 +            } else {
   1.148 +                // an install is already happening, so we should let it complete.
   1.149 +                Log.i(LOGTAG, "Waiting for existing install to complete");
   1.150 +                installHelper.registerGeckoListener();
   1.151 +            }
   1.152 +            return;
   1.153 +        } else {
   1.154 +            launchWebapp(origin);
   1.155 +        }
   1.156 +
   1.157 +        setTitle(mAppName);
   1.158 +    }
   1.159 +
   1.160 +    @Override
   1.161 +    protected String getURIFromIntent(Intent intent) {
   1.162 +        String uri = super.getURIFromIntent(intent);
   1.163 +        if (uri != null) {
   1.164 +            return uri;
   1.165 +        }
   1.166 +        // This is where we construct the URL from the Intent from the
   1.167 +        // the synthesized APK.
   1.168 +
   1.169 +        // TODO Translate AndroidIntents into WebActivities here.
   1.170 +        if (mIsApk) {
   1.171 +            return mApkResources.getManifestUrl();
   1.172 +        }
   1.173 +
   1.174 +        // If this is a legacy shortcut, then we should have been able to get
   1.175 +        // the URI from the intent data.  Otherwise, we should have been able
   1.176 +        // to get it from the APK resources.  So we should never get here.
   1.177 +        Log.wtf(LOGTAG, "Couldn't get URI from intent nor APK resources");
   1.178 +        return null;
   1.179 +    }
   1.180 +
   1.181 +    @Override
   1.182 +    protected void loadStartupTab(String uri) {
   1.183 +        // Load a tab so it's available for any code that assumes a tab
   1.184 +        // before the app tab itself is loaded in BrowserApp._loadWebapp.
   1.185 +        super.loadStartupTab("about:blank");
   1.186 +    }
   1.187 +
   1.188 +    private void showSplash() {
   1.189 +
   1.190 +        // get the favicon dominant color, stored when the app was installed
   1.191 +        int dominantColor = Allocator.getInstance().getColor(getIndex());
   1.192 +
   1.193 +        setBackgroundGradient(dominantColor);
   1.194 +
   1.195 +        ImageView image = (ImageView)findViewById(R.id.splashscreen_icon);
   1.196 +        Drawable d = null;
   1.197 +
   1.198 +        if (mIsApk) {
   1.199 +            Uri uri = mApkResources.getAppIconUri();
   1.200 +            image.setImageURI(uri);
   1.201 +            d = image.getDrawable();
   1.202 +        } else {
   1.203 +            // look for a logo.png in the profile dir and show it. If we can't find a logo show nothing
   1.204 +            File profile = getProfile().getDir();
   1.205 +            File logoFile = new File(profile, "logo.png");
   1.206 +            if (logoFile.exists()) {
   1.207 +                d = Drawable.createFromPath(logoFile.getPath());
   1.208 +                image.setImageDrawable(d);
   1.209 +            }
   1.210 +        }
   1.211 +
   1.212 +        if (d != null) {
   1.213 +            Animation fadein = AnimationUtils.loadAnimation(this, R.anim.grow_fade_in_center);
   1.214 +            fadein.setStartOffset(500);
   1.215 +            fadein.setDuration(1000);
   1.216 +            image.startAnimation(fadein);
   1.217 +        }
   1.218 +    }
   1.219 +
   1.220 +    public void setBackgroundGradient(int dominantColor) {
   1.221 +        int[] colors = new int[2];
   1.222 +        // now lighten it, to ensure that the icon stands out in the center
   1.223 +        float[] f = new float[3];
   1.224 +        Color.colorToHSV(dominantColor, f);
   1.225 +        f[2] = Math.min(f[2]*2, 1.0f);
   1.226 +        colors[0] = Color.HSVToColor(255, f);
   1.227 +
   1.228 +        // now generate a second, slightly darker version of the same color
   1.229 +        f[2] *= 0.75;
   1.230 +        colors[1] = Color.HSVToColor(255, f);
   1.231 +
   1.232 +        // Draw the background gradient
   1.233 +        GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TL_BR, colors);
   1.234 +        gd.setGradientType(GradientDrawable.RADIAL_GRADIENT);
   1.235 +        Display display = getWindowManager().getDefaultDisplay();
   1.236 +        gd.setGradientCenter(0.5f, 0.5f);
   1.237 +        gd.setGradientRadius(Math.max(display.getWidth()/2, display.getHeight()/2));
   1.238 +        mSplashscreen.setBackgroundDrawable(gd);
   1.239 +    }
   1.240 +
   1.241 +    /* (non-Javadoc)
   1.242 +     * @see org.mozilla.gecko.GeckoApp#getDefaultProfileName()
   1.243 +     */
   1.244 +    @Override
   1.245 +    protected String getDefaultProfileName() {
   1.246 +        return "webapp" + getIndex();
   1.247 +    }
   1.248 +
   1.249 +    @Override
   1.250 +    protected boolean getSessionRestoreState(Bundle savedInstanceState) {
   1.251 +        // for now webapps never restore your session
   1.252 +        return false;
   1.253 +    }
   1.254 +
   1.255 +    @Override
   1.256 +    public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
   1.257 +        switch(msg) {
   1.258 +            case SELECTED:
   1.259 +            case LOCATION_CHANGE:
   1.260 +                if (Tabs.getInstance().isSelectedTab(tab)) {
   1.261 +                    final String urlString = tab.getURL();
   1.262 +
   1.263 +                    // Don't show the titlebar for about:blank, which we load
   1.264 +                    // into the initial tab we create while waiting for the app
   1.265 +                    // to load.
   1.266 +                    if (urlString != null && urlString.equals("about:blank")) {
   1.267 +                        mTitlebar.setVisibility(View.GONE);
   1.268 +                        return;
   1.269 +                    }
   1.270 +
   1.271 +                    final URI uri;
   1.272 +
   1.273 +                    try {
   1.274 +                        uri = new URI(urlString);
   1.275 +                    } catch (java.net.URISyntaxException ex) {
   1.276 +                        mTitlebarText.setText(urlString);
   1.277 +
   1.278 +                        // If we can't parse the url, and its an app protocol hide
   1.279 +                        // the titlebar and return, otherwise show the titlebar
   1.280 +                        // and the full url
   1.281 +                        if (urlString != null && !urlString.startsWith("app://")) {
   1.282 +                            mTitlebar.setVisibility(View.VISIBLE);
   1.283 +                        } else {
   1.284 +                            mTitlebar.setVisibility(View.GONE);
   1.285 +                        }
   1.286 +                        return;
   1.287 +                    }
   1.288 +
   1.289 +                    if (mOrigin != null && mOrigin.getHost().equals(uri.getHost())) {
   1.290 +                        mTitlebar.setVisibility(View.GONE);
   1.291 +                    } else {
   1.292 +                        mTitlebarText.setText(uri.getScheme() + "://" + uri.getHost());
   1.293 +                        mTitlebar.setVisibility(View.VISIBLE);
   1.294 +                    }
   1.295 +                }
   1.296 +                break;
   1.297 +            case LOADED:
   1.298 +                hideSplash();
   1.299 +                break;
   1.300 +            case START:
   1.301 +                if (mSplashscreen != null && mSplashscreen.getVisibility() == View.VISIBLE) {
   1.302 +                    View area = findViewById(R.id.splashscreen_progress);
   1.303 +                    area.setVisibility(View.VISIBLE);
   1.304 +                    Animation fadein = AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
   1.305 +                    fadein.setDuration(1000);
   1.306 +                    area.startAnimation(fadein);
   1.307 +                }
   1.308 +                break;
   1.309 +        }
   1.310 +        super.onTabChanged(tab, msg, data);
   1.311 +    }
   1.312 +
   1.313 +    protected void hideSplash() {
   1.314 +        if (mSplashscreen != null && mSplashscreen.getVisibility() == View.VISIBLE) {
   1.315 +            Animation fadeout = AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
   1.316 +            fadeout.setAnimationListener(new Animation.AnimationListener() {
   1.317 +                @Override
   1.318 +                public void onAnimationEnd(Animation animation) {
   1.319 +                  mSplashscreen.setVisibility(View.GONE);
   1.320 +                }
   1.321 +                @Override
   1.322 +                public void onAnimationRepeat(Animation animation) { }
   1.323 +                @Override
   1.324 +                public void onAnimationStart(Animation animation) { }
   1.325 +            });
   1.326 +            mSplashscreen.startAnimation(fadeout);
   1.327 +        }
   1.328 +    }
   1.329 +
   1.330 +    @Override
   1.331 +    public void installCompleted(InstallHelper installHelper, String event, JSONObject message) {
   1.332 +        if (event == null) {
   1.333 +            return;
   1.334 +        }
   1.335 +
   1.336 +        if (event.equals("Webapps:Postinstall")) {
   1.337 +            String origin = message.optString("origin");
   1.338 +            launchWebapp(origin);
   1.339 +        }
   1.340 +    }
   1.341 +
   1.342 +    @Override
   1.343 +    public void installErrored(InstallHelper installHelper, Exception exception) {
   1.344 +        Log.e(LOGTAG, "Install errored", exception);
   1.345 +    }
   1.346 +
   1.347 +    private void setOrigin(String origin) {
   1.348 +        try {
   1.349 +            mOrigin = new URI(origin);
   1.350 +        } catch (java.net.URISyntaxException ex) {
   1.351 +            // If this isn't an app: URL, just settle for not having an origin.
   1.352 +            if (!origin.startsWith("app://")) {
   1.353 +                return;
   1.354 +            }
   1.355 +
   1.356 +            // If that failed fall back to the origin stored in the shortcut.
   1.357 +            if (!mIsApk) {
   1.358 +                Log.i(LOGTAG, "Origin is app: URL; falling back to intent URL");
   1.359 +                Uri data = getIntent().getData();
   1.360 +                if (data != null) {
   1.361 +                    try {
   1.362 +                        mOrigin = new URI(data.toString());
   1.363 +                    } catch (java.net.URISyntaxException ex2) {
   1.364 +                        Log.e(LOGTAG, "Unable to parse intent URL: ", ex);
   1.365 +                    }
   1.366 +                }
   1.367 +            }
   1.368 +        }
   1.369 +    }
   1.370 +
   1.371 +    public void launchWebapp(String origin) {
   1.372 +        setOrigin(origin);
   1.373 +
   1.374 +        try {
   1.375 +            JSONObject launchObject = new JSONObject();
   1.376 +            launchObject.putOpt("url", mManifestUrl);
   1.377 +            launchObject.putOpt("name", mAppName);
   1.378 +            Log.i(LOGTAG, "Trying to launch: " + launchObject);
   1.379 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Webapps:Load", launchObject.toString()));
   1.380 +        } catch (JSONException e) {
   1.381 +            Log.e(LOGTAG, "Error populating launch message", e);
   1.382 +        }
   1.383 +    }
   1.384 +
   1.385 +    @Override
   1.386 +    protected boolean getIsDebuggable() {
   1.387 +        if (mIsApk) {
   1.388 +            return mApkResources.isDebuggable();
   1.389 +        }
   1.390 +
   1.391 +        // This is a legacy shortcut, which didn't provide a way to determine
   1.392 +        // that the app is debuggable, so we say the app is not debuggable.
   1.393 +        return false;
   1.394 +    }
   1.395 +}

mercurial