Bug 1336355 - Open webapps in new activity. r=sebastian

This commit is contained in:
Dale Harvey 2017-03-15 15:39:01 +00:00
parent ceeedc0eb2
commit 6895048221
11 changed files with 223 additions and 6 deletions

View File

@ -90,8 +90,15 @@ class Manifest {
}
async icon(expectedSize) {
return await ManifestIcons
if ('cached_icon' in this._store.data) {
return this._store.data.cached_icon;
}
const icon = await ManifestIcons
.browserFetchIcon(this._browser, this._store.data.manifest, expectedSize);
// Cache the icon so future requests do not go over the network
this._store.data.cached_icon = icon;
this._store.saveSoon();
return icon;
}
get scope() {
@ -116,6 +123,10 @@ class Manifest {
get start_url() {
return this._store.data.manifest.start_url;
}
get path() {
return this._path;
}
}
/*

View File

@ -63,7 +63,7 @@ async function getIcon(aWindow, icons, expectedSize) {
return fetchIcon(aWindow, icons[index].src).catch(err => {
// Remove all icons with the failed source, the same source
// may have been used for multiple sizes
icons = icons.filter(x => x.src === icons[index].src);
icons = icons.filter(x => x.src !== icons[index].src);
return getIcon(aWindow, icons, expectedSize);
});
}

View File

@ -54,6 +54,7 @@
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:relinquishTaskIdentity="true"
android:taskAffinity=""
android:exported="true"
android:excludeFromRecents="true" />
<!-- Fennec is shipped as the Android package named
@ -316,6 +317,8 @@
<activity android:name="org.mozilla.gecko.customtabs.CustomTabsActivity"
android:theme="@style/GeckoCustomTabs" />
#endif
<activity android:name="org.mozilla.gecko.webapps.WebAppActivity"
android:theme="@style/Theme.AppCompat.NoActionBar" />
<!-- Service to handle requests from overlays. -->
<service android:name="org.mozilla.gecko.overlays.service.OverlayActionService" />

View File

@ -2037,10 +2037,11 @@ public class BrowserApp extends GeckoApp
case "Website:AppInstalled":
final String name = message.getString("name");
final String startUrl = message.getString("start_url");
final String manifestPath = message.getString("manifest_path");
final Bitmap icon = FaviconDecoder
.decodeDataURI(getContext(), message.getString("icon"))
.getBestBitmap(GeckoAppShell.getPreferredIconSize());
createShortcut(name, startUrl, icon);
createAppShortcut(name, startUrl, manifestPath, icon);
break;
case "Updater:Launch":

View File

@ -40,12 +40,14 @@ import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.ColorUtil;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.FileUtils;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.PrefUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.webapps.WebAppActivity;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@ -145,6 +147,7 @@ public abstract class GeckoApp
public static final String ACTION_ALERT_CALLBACK = "org.mozilla.gecko.ALERT_CALLBACK";
public static final String ACTION_HOMESCREEN_SHORTCUT = "org.mozilla.gecko.BOOKMARK";
public static final String ACTION_WEBAPP = "org.mozilla.gecko.WEBAPP";
public static final String ACTION_DEBUG = "org.mozilla.gecko.DEBUG";
public static final String ACTION_LAUNCH_SETTINGS = "org.mozilla.gecko.SETTINGS";
public static final String ACTION_LOAD = "org.mozilla.gecko.LOAD";
@ -2022,13 +2025,25 @@ public abstract class GeckoApp
}
public void createShortcut(final String aTitle, final String aURI, final Bitmap aIcon) {
// The intent to be launched by the shortcut.
Intent shortcutIntent = new Intent();
shortcutIntent.setAction(GeckoApp.ACTION_HOMESCREEN_SHORTCUT);
shortcutIntent.setData(Uri.parse(aURI));
shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
createHomescreenIcon(shortcutIntent, aTitle, aURI, aIcon);
}
public void createAppShortcut(final String aTitle, final String aURI, final String manifestPath, final Bitmap aIcon) {
Intent shortcutIntent = new Intent();
shortcutIntent.setAction(GeckoApp.ACTION_WEBAPP);
shortcutIntent.setData(Uri.parse(aURI));
shortcutIntent.putExtra("MANIFEST_PATH", manifestPath);
shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, LauncherActivity.class.getName());
createHomescreenIcon(shortcutIntent, aTitle, aURI, aIcon);
}
public void createHomescreenIcon(final Intent shortcutIntent, final String aTitle,
final String aURI, final Bitmap aIcon) {
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, GeckoAppShell.getPreferredIconSize()));

View File

@ -11,6 +11,7 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.customtabs.CustomTabsIntent;
import org.mozilla.gecko.webapps.WebAppActivity;
import org.mozilla.gecko.customtabs.CustomTabsActivity;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.mozglue.SafeIntent;
@ -30,8 +31,12 @@ public class LauncherActivity extends Activity {
final SafeIntent safeIntent = new SafeIntent(getIntent());
// Is this web app?
if (isWebAppIntent(safeIntent)) {
dispatchWebAppIntent();
// If it's not a view intent, it won't be a custom tabs intent either. Just launch!
if (!isViewIntentWithURL(safeIntent)) {
} else if (!isViewIntentWithURL(safeIntent)) {
dispatchNormalIntent();
// Is this a custom tabs intent, and are custom tabs enabled?
@ -83,6 +88,15 @@ public class LauncherActivity extends Activity {
startActivity(intent);
}
private void dispatchWebAppIntent() {
Intent intent = new Intent(getIntent());
intent.setClassName(getApplicationContext(), WebAppActivity.class.getName());
filterFlags(intent);
startActivity(intent);
}
private static void filterFlags(Intent intent) {
// Explicitly remove the new task and clear task flags (Our browser activity is a single
// task activity and we never want to start a second task here). See bug 1280112.
@ -104,6 +118,10 @@ public class LauncherActivity extends Activity {
&& safeIntent.hasExtra(CustomTabsIntent.EXTRA_SESSION);
}
private static boolean isWebAppIntent(@NonNull final SafeIntent safeIntent) {
return GeckoApp.ACTION_WEBAPP.equals(safeIntent.getAction());
}
private boolean isCustomTabsEnabled() {
return GeckoSharedPrefs.forApp(this).getBoolean(GeckoPreferences.PREFS_CUSTOM_TABS, false);
}

View File

@ -29,6 +29,19 @@ public class ColorUtil {
}
}
public static Integer parseStringColor(final String color) {
try {
if (color.length() < 7) {
return null;
}
return Color.argb(255,
Integer.valueOf(color.substring(1, 3), 16),
Integer.valueOf(color.substring(3, 5), 16),
Integer.valueOf(color.substring(5, 7), 16));
} catch (NumberFormatException e) { }
return null;
}
private static int darkenColor(final int color, final double fraction) {
return (int) Math.max(color - (color * fraction), 0);
}

View File

@ -0,0 +1,112 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.webapps;
import java.io.File;
import java.io.IOException;
import android.app.ActivityManager;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.util.Log;
import org.json.JSONObject;
import org.json.JSONException;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.icons.decoders.FaviconDecoder;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ColorUtil;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.FileUtils;
import org.mozilla.gecko.util.GeckoBundle;
public class WebAppActivity extends GeckoApp {
public static final String INTENT_KEY = "IS_A_WEBAPP";
public static final String MANIFEST_PATH = "MANIFEST_PATH";
private static final String LOGTAG = "WebAppActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String manifestPath = getIntent().getStringExtra(WebAppActivity.MANIFEST_PATH);
if (manifestPath != null) {
updateFromManifest(manifestPath);
}
}
@Override
public int getLayout() {
return R.layout.webapp_activity;
}
private void updateFromManifest(String manifestPath) {
try {
final File manifestFile = new File(manifestPath);
final JSONObject manifest = FileUtils.readJSONObjectFromFile(manifestFile);
final JSONObject manifestField = (JSONObject) manifest.get("manifest");
final Integer color = readColorFromManifest(manifestField);
final String name = readNameFromManifest(manifestField);
final Bitmap icon = readIconFromManifest(manifest);
final ActivityManager.TaskDescription taskDescription = (color == null)
? new ActivityManager.TaskDescription(name, icon)
: new ActivityManager.TaskDescription(name, icon, color);
updateStatusBarColor(color);
setTaskDescription(taskDescription);
} catch (IOException | JSONException e) {
Log.e(LOGTAG, "Failed to read manifest", e);
}
}
private void updateStatusBarColor(final Integer themeColor) {
if (themeColor != null && !AppConstants.Versions.preLollipop) {
final Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(ColorUtil.darken(themeColor, 0.25));
}
}
private Integer readColorFromManifest(JSONObject manifest) throws JSONException {
final String colorStr = (String) manifest.get("theme_color");
if (colorStr != null) {
return ColorUtil.parseStringColor(colorStr);
}
return null;
}
private String readNameFromManifest(JSONObject manifest) throws JSONException {
String name = (String) manifest.get("name");
if (name == null) {
name = (String) manifest.get("short_name");
}
if (name == null) {
name = (String) manifest.get("start_url");
}
return name;
}
private Bitmap readIconFromManifest(JSONObject manifest) throws JSONException {
final String iconStr = (String) manifest.get("cached_icon");
if (iconStr != null) {
return FaviconDecoder
.decodeDataURI(getContext(), iconStr)
.getBestBitmap(GeckoAppShell.getPreferredIconSize());
}
return null;
}
}

View File

@ -789,6 +789,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'util/ResourceDrawableUtils.java',
'util/TouchTargetUtil.java',
'util/ViewUtil.java',
'webapps/WebAppActivity.java',
'widget/ActivityChooserModel.java',
'widget/AllCapsTextView.java',
'widget/AnchoredPopup.java',

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
This layout is quite complex because GeckoApp accesses all view groups
in this tree. In a perfect world this should just include a GeckoView.
-->
<view class="org.mozilla.gecko.GeckoApp$MainLayout"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_below="@+id/toolbar"
android:layout_height="match_parent"
android:background="@android:color/transparent">
<RelativeLayout android:id="@+id/gecko_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/tablet_tab_strip"
android:layout_above="@+id/find_in_page">
<fragment class="org.mozilla.gecko.GeckoViewFragment"
android:id="@+id/layer_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"/>
</RelativeLayout>
</view>
</RelativeLayout>

View File

@ -2208,7 +2208,8 @@ async function installManifest(browser, manifestUrl, iconSize) {
type: "Website:AppInstalled",
icon,
name: manifest.name,
start_url: manifest.start_url
start_url: manifest.start_url,
manifest_path: manifest.path
});
} catch (err) {
Cu.reportError("Failed to install: " + err.message);